Files

95 lines
4.6 KiB
Python
Raw Permalink Normal View History

"""Baidu-hosted UI images. DB stores references; only a bounded RAM cache holds bytes."""
import base64
import hashlib
import re
import secrets
import threading
import time
from collections import OrderedDict
from appearance import png_bytes
class CloudImages:
def __init__(self, app):
self.a = app
self.guard = threading.RLock()
self.cache = OrderedDict()
self.limit = 16*1024*1024
def remember(self, ref, raw):
with self.guard:
now=time.monotonic()
for k,(until,_) in list(self.cache.items()):
if until<now:self.cache.pop(k)
self.cache[ref['sha256']] = (now+600,raw)
self.cache.move_to_end(ref['sha256'])
while sum(len(v[1]) for v in self.cache.values())>self.limit:self.cache.popitem(last=False)
def get(self, ref):
with self.guard:
found=self.cache.get(ref['sha256'])
if found and found[0]>time.monotonic():return found[1]
with self.a.BAIDU.open_file('shared',ref) as response:
raw=response.read(ref['size']+1)
if len(raw)!=ref['size'] or hashlib.sha256(raw).hexdigest()!=ref['sha256']:
raise self.a.Problem('网盘图片校验未通过,请重新上传',502)
self.remember(ref,raw)
return raw
def put(self, value, scope, kind, maximum=2*1024*1024):
if kind not in ('封面','Logo','图标') or not re.fullmatch(r'[a-f0-9]{16}|platform',scope):
raise self.a.Problem('图片归属不正确')
try:raw=png_bytes(value,maximum)
except ValueError as error:raise self.a.Problem(str(error).replace('Logo',kind))
a=self.a
a.BAIDU.token('shared')
with a.LOCK:
instance=a.setting('cloud_instance')
if not instance:instance=secrets.token_hex(8);a.set_setting('cloud_instance',instance)
digest=hashlib.sha256(raw).hexdigest()
category='平台外观' if scope=='platform' else '家庭与场所'
remote='/apps/'+a.BAIDU.public_config()['appFolder']+'/'+category+'/'+instance+'/'+scope+'/'+kind+'/'+digest+'.png'
md5=hashlib.md5(raw).hexdigest();hashes=[md5]
pre=a.BAIDU.prepare(remote,len(raw),hashes)
result=pre.get('existing')
if not result:
needed=pre.get('block_list',[0])
if not isinstance(needed,list) or any(type(i) is not int or i!=0 for i in needed):raise a.Problem('网盘图片分片信息不正确',502)
if 0 in needed:a.BAIDU.send_block(remote,pre['uploadid'],0,raw,md5)
result=a.BAIDU.complete(dict(remotePath=remote,size=len(raw),uploadId=pre['uploadid'],hashes=hashes))
if not result.get('fs_id') or result.get('path')!=remote or result.get('size')!=len(raw):
raise a.Problem('网盘尚未确认图片,请重试',502)
ref=dict(provider='baidu',fsId=str(result['fs_id']),remotePath=remote,size=len(raw),sha256=digest,mime='image/png')
# Verify by reading the remote file before committing the reference, including retries.
with a.BAIDU.open_file('shared',ref) as response:
check=response.read(len(raw)+1)
if check!=raw:raise a.Problem('网盘图片回读校验失败,原有封面未改变',502)
self.remember(ref,raw)
return ref
def migrate_legacy(self):
"""Explicit admin maintenance; replace each reference only after cloud verification."""
a=self.a;result=[]
for home in a.objects('households'):
value=home.get('coverData')
if not value:continue
ref=self.put(value,home['id'],'封面')
with a.LOCK:
current=a.get_object('households',home['id'])
if current.get('coverData')!=value:continue
current.pop('coverData',None);current['coverRef']=ref
current['profileRevision']=current.get('profileRevision',0)+1
current['coverRevision']=ref['sha256'][:16];a.save_object('households',current)
result.append('household:'+home['id'])
for kind,field in [('Logo','logo'),('图标','icon')]:
old=a.APPEARANCE.config();value=old.get(field+'Data')
if not value:continue
ref=self.put(value,'platform',kind,256*1024)
with a.LOCK:
current=a.APPEARANCE.config()
if current.get(field+'Data')!=value:continue
current.pop(field+'Data',None);current[field+'Ref']=ref
current['revision']+=1;a.set_setting('appearance',current)
result.append('brand:'+field)
return result