feat: add private journals and centralized Baidu media storage for v0.1.28
This commit is contained in:
@@ -36,6 +36,8 @@ import inventory
|
||||
import heritage
|
||||
import appearance
|
||||
import backups
|
||||
import baidu
|
||||
import journal
|
||||
import ptz
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
@@ -83,6 +85,8 @@ INVENTORY = inventory.Inventory(sys.modules[__name__])
|
||||
HERITAGE = heritage.Heritage(sys.modules[__name__])
|
||||
APPEARANCE = appearance.Appearance(sys.modules[__name__])
|
||||
BACKUPS = backups.Backups(sys.modules[__name__])
|
||||
BAIDU = baidu.Baidu(sys.modules[__name__])
|
||||
JOURNAL = journal.Journal(sys.modules[__name__])
|
||||
|
||||
|
||||
def setting(key, default=None):
|
||||
@@ -629,15 +633,28 @@ class Handler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
try:
|
||||
path = url.urlsplit(self.path).path
|
||||
if path == '/api/journal/chunk':
|
||||
self.validate_origin(); self.require_auth()
|
||||
query = dict(url.parse_qsl(url.urlsplit(self.path).query))
|
||||
try:
|
||||
size = int(self.headers.get('Content-Length', '0')); offset = int(query.get('offset', '-1'))
|
||||
except ValueError: raise Problem('上传分片参数不正确')
|
||||
if not 0 < size <= journal.CHUNK: raise Problem('上传分片大小不正确')
|
||||
self.connection.settimeout(45)
|
||||
raw = self.rfile.read(size)
|
||||
if len(raw) != size: raise Problem('分片未完整接收,请重试')
|
||||
self.answer(JOURNAL.chunk(self.user, query.get('id'), offset, raw))
|
||||
return
|
||||
# 12,000 Chinese characters plus JSON escapes and linked people exceed 16 KiB.
|
||||
data = self.body(3*1024*1024 if path in ('/api/appearance','/api/households') else 131072 if path in ('/api/family/event','/api/heritage') else 16384)
|
||||
data = self.body(3*1024*1024 if path in ('/api/appearance','/api/households') else 131072 if path in ('/api/family/event','/api/heritage','/api/journal') else 16384)
|
||||
self.validate_origin()
|
||||
if path in ('/api/login', '/api/setup'):
|
||||
self.login(data, path == '/api/setup')
|
||||
return
|
||||
self.require_auth()
|
||||
if path not in ('/api/logout', '/api/account/password', '/api/preferences', '/api/family/restore',
|
||||
'/api/family/person', '/api/family/link', '/api/family/event', '/api/heritage', '/api/ptz', '/api/ptz/presets'):
|
||||
'/api/family/person', '/api/family/link', '/api/family/event', '/api/heritage', '/api/ptz', '/api/ptz/presets',
|
||||
'/api/journal', '/api/journal/action', '/api/journal/upload', '/api/journal/finish', '/api/journal/remove-upload'):
|
||||
self.require_manager()
|
||||
if path == '/api/logout':
|
||||
with LOCK:
|
||||
@@ -646,6 +663,24 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.answer({'ok': True}, cookie='vision=; Path=/; Max-Age=0; HttpOnly; SameSite=Strict')
|
||||
elif path == '/api/preferences':
|
||||
self.answer(APPEARANCE.save_preferences(data, self.user))
|
||||
elif path == '/api/baidu/config':
|
||||
self.require_admin(); self.answer(BAIDU.configure(data, self.user))
|
||||
elif path == '/api/baidu/connect':
|
||||
self.require_admin()
|
||||
result, cookie = BAIDU.begin(self.user, 'media', self.token(), self.headers.get('Host'))
|
||||
self.answer(result, cookie=cookie)
|
||||
elif path == '/api/baidu/disconnect':
|
||||
self.require_admin(); self.answer(BAIDU.disconnect(self.user, 'media'))
|
||||
elif path == '/api/journal':
|
||||
self.answer(JOURNAL.save(self.user, data))
|
||||
elif path == '/api/journal/action':
|
||||
self.answer(JOURNAL.action(self.user, data))
|
||||
elif path == '/api/journal/upload':
|
||||
self.answer(JOURNAL.begin_upload(self.user, data))
|
||||
elif path == '/api/journal/finish':
|
||||
self.answer(JOURNAL.finish_upload(self.user, data.get('id')))
|
||||
elif path == '/api/journal/remove-upload':
|
||||
self.answer(JOURNAL.remove_upload(self.user, data.get('id')))
|
||||
elif path == '/api/appearance':
|
||||
self.require_admin()
|
||||
self.answer(APPEARANCE.save_config(data, self.user))
|
||||
@@ -841,6 +876,17 @@ class Handler(BaseHTTPRequestHandler):
|
||||
parsed = url.urlsplit(self.path)
|
||||
path = parsed.path
|
||||
query = {k: v[0] for k, v in url.parse_qs(parsed.query).items()}
|
||||
if path == baidu.CALLBACK:
|
||||
try:
|
||||
BAIDU.callback(query, self.headers.get('Cookie', '')); result = 'ok'
|
||||
except Problem: result = 'failed'
|
||||
self.send_response(303)
|
||||
self.send_header('Location', '/?baidu='+result)
|
||||
self.send_header('Cache-Control', 'no-store')
|
||||
self.send_header('Referrer-Policy', 'no-referrer')
|
||||
self.send_header('Set-Cookie', 'baidu_flow=; Path=/api/baidu/callback; HttpOnly; SameSite=Lax; Secure; Max-Age=0')
|
||||
self.send_header('Content-Length', '0'); self.end_headers()
|
||||
return
|
||||
if path == '/api/branding':
|
||||
self.answer(APPEARANCE.public())
|
||||
return
|
||||
@@ -862,6 +908,34 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.require_auth()
|
||||
if path == '/api/state':
|
||||
self.answer(ACCOUNTS.state(self.user, status()))
|
||||
elif path == '/api/baidu/config':
|
||||
self.require_admin(); self.answer(BAIDU.public_config())
|
||||
elif path == '/api/baidu/status':
|
||||
self.answer(BAIDU.status(self.user))
|
||||
elif path == '/api/journal':
|
||||
self.answer(JOURNAL.snapshot(self.user, query))
|
||||
elif path == '/media/journal':
|
||||
self.user = HOUSEHOLDS.context(self.user, query.get('family'))
|
||||
item = JOURNAL.media(self.user, query.get('id'))
|
||||
with BAIDU.open_file('shared', item, self.headers.get('Range', '')) as response:
|
||||
if response.status not in (200, 206): raise Problem('网盘媒体暂时不可用', 502)
|
||||
self.send_response(response.status)
|
||||
self.send_header('Content-Type', item['mime'])
|
||||
self.send_header('Cache-Control', 'no-store')
|
||||
self.send_header('X-Content-Type-Options', 'nosniff')
|
||||
self.send_header('Referrer-Policy', 'no-referrer')
|
||||
self.send_header('Accept-Ranges', 'bytes')
|
||||
self.send_header('Content-Disposition', ('attachment' if query.get('download') == '1' else 'inline')+"; filename*=UTF-8''"+url.quote(item['name']))
|
||||
for header in ('Content-Length', 'Content-Range'):
|
||||
if response.headers.get(header): self.send_header(header, response.headers[header])
|
||||
self.end_headers()
|
||||
remaining = item['size']
|
||||
try:
|
||||
while remaining > 0:
|
||||
chunk = response.read(min(65536, remaining))
|
||||
if not chunk: break
|
||||
self.wfile.write(chunk); remaining -= len(chunk)
|
||||
except (OSError, http.client.HTTPException): self.close_connection = True
|
||||
elif path == '/api/preferences':
|
||||
self.answer(APPEARANCE.preferences(self.user))
|
||||
elif path == '/api/appearance':
|
||||
@@ -970,6 +1044,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
'/family.js': 'family.js', '/kinship.js': 'kinship.js', '/households.js': 'households.js',
|
||||
'/device-model.js': 'device-model.js', '/devices.js': 'devices.js',
|
||||
'/steward.js': 'steward.js', '/heritage.js': 'heritage.js', '/personalization.js': 'personalization.js',
|
||||
'/journal.js': 'journal.js', '/file-hash.js': 'file-hash.js',
|
||||
'/family-graph.js': 'family-graph.js', '/family-map.js': 'family-map.js',
|
||||
'/family-map.css': 'family-map.css', '/chinese-theme.css': 'chinese-theme.css',
|
||||
'/family-events.js': 'family-events.js',
|
||||
@@ -1009,6 +1084,7 @@ def initialize():
|
||||
ACCOUNTS.initialize()
|
||||
HOUSEHOLDS.initialize()
|
||||
HERITAGE.initialize()
|
||||
JOURNAL.initialize()
|
||||
if not setting('storage'):
|
||||
set_setting('storage', {'retentionDays': 7, 'maxGB': 40, 'reserveGB': 8})
|
||||
if not setting('mediaSecret'):
|
||||
|
||||
Reference in New Issue
Block a user