Improved image proxying

This commit is contained in:
krateng 2023-04-01 15:28:33 +02:00
parent 61d3015443
commit 31661c4141
3 changed files with 48 additions and 30 deletions

0
maloja/data_files/cache/images/dummy vendored Normal file
View File

View File

@ -21,9 +21,14 @@ import sqlalchemy as sql
# remove old db file (columns missing)
try:
os.remove(data_dir['cache']('images.sqlite'))
except:
pass
DB = {} DB = {}
engine = sql.create_engine(f"sqlite:///{data_dir['cache']('images.sqlite')}", echo = False) engine = sql.create_engine(f"sqlite:///{data_dir['cache']('imagecache.sqlite')}", echo = False)
meta = sql.MetaData() meta = sql.MetaData()
dblock = Lock() dblock = Lock()
@ -33,51 +38,61 @@ DB['artists'] = sql.Table(
sql.Column('id',sql.Integer,primary_key=True), sql.Column('id',sql.Integer,primary_key=True),
sql.Column('url',sql.String), sql.Column('url',sql.String),
sql.Column('expire',sql.Integer), sql.Column('expire',sql.Integer),
sql.Column('raw',sql.String) # sql.Column('raw',sql.String)
sql.Column('local',sql.Boolean),
sql.Column('localproxyurl',sql.String)
) )
DB['tracks'] = sql.Table( DB['tracks'] = sql.Table(
'tracks', meta, 'tracks', meta,
sql.Column('id',sql.Integer,primary_key=True), sql.Column('id',sql.Integer,primary_key=True),
sql.Column('url',sql.String), sql.Column('url',sql.String),
sql.Column('expire',sql.Integer), sql.Column('expire',sql.Integer),
sql.Column('raw',sql.String) # sql.Column('raw',sql.String)
sql.Column('local',sql.Boolean),
sql.Column('localproxyurl',sql.String)
) )
DB['albums'] = sql.Table( DB['albums'] = sql.Table(
'albums', meta, 'albums', meta,
sql.Column('id',sql.Integer,primary_key=True), sql.Column('id',sql.Integer,primary_key=True),
sql.Column('url',sql.String), sql.Column('url',sql.String),
sql.Column('expire',sql.Integer), sql.Column('expire',sql.Integer),
sql.Column('raw',sql.String) # sql.Column('raw',sql.String)
sql.Column('local',sql.Boolean),
sql.Column('localproxyurl',sql.String)
) )
meta.create_all(engine) meta.create_all(engine)
def get_image_from_cache(track_id=None,artist_id=None,album_id=None): def get_image_from_cache(track_id=None,artist_id=None,album_id=None):
now = int(datetime.datetime.now().timestamp()) now = int(datetime.datetime.now().timestamp())
if track_id: if track_id:
table = 'tracks' table = 'tracks'
id = track_id entity_id = track_id
elif album_id: elif album_id:
table = 'albums' table = 'albums'
id = album_id entity_id = album_id
elif artist_id: elif artist_id:
table = 'artists' table = 'artists'
id = artist_id entity_id = artist_id
with engine.begin() as conn: with engine.begin() as conn:
op = DB[table].select().where( op = DB[table].select().where(
DB[table].c.id==id, DB[table].c.id==entity_id,
DB[table].c.expire>now DB[table].c.expire>now
) )
result = conn.execute(op).all() result = conn.execute(op).all()
for row in result: for row in result:
if row.raw is not None: if row.local:
return {'type':'raw','value':row.raw} return {'type':'localurl','value':row.url}
elif row.localproxyurl:
return {'type':'localurl','value':row.localproxyurl}
else: else:
return {'type':'url','value':row.url} # returns None as value if nonexistence cached return {'type':'url','value':row.url} # returns None as value if nonexistence cached
return None # no cache entry return None # no cache entry
def set_image_in_cache(id,table,url): def set_image_in_cache(id,table,url,local=False):
remove_image_from_cache(id,table) remove_image_from_cache(id,table)
with dblock: with dblock:
now = int(datetime.datetime.now().timestamp()) now = int(datetime.datetime.now().timestamp())
@ -86,14 +101,18 @@ def set_image_in_cache(id,table,url):
else: else:
expire = now + (malojaconfig["CACHE_EXPIRE_POSITIVE"] * 24 * 3600) expire = now + (malojaconfig["CACHE_EXPIRE_POSITIVE"] * 24 * 3600)
raw = dl_image(url) if not local and malojaconfig["PROXY_IMAGES"] and url is not None:
localproxyurl = dl_image(url)
else:
localproxyurl = None
with engine.begin() as conn: with engine.begin() as conn:
op = DB[table].insert().values( op = DB[table].insert().values(
id=id, id=id,
url=url, url=url,
expire=expire, expire=expire,
raw=raw local=local,
localproxyurl=localproxyurl
) )
result = conn.execute(op) result = conn.execute(op)
@ -105,17 +124,19 @@ def remove_image_from_cache(id,table):
) )
result = conn.execute(op) result = conn.execute(op)
# TODO delete proxy
def dl_image(url): def dl_image(url):
if not malojaconfig["PROXY_IMAGES"]: return None
if url is None: return None
if url.startswith("/"): return None #local image
try: try:
r = requests.get(url) r = requests.get(url)
mime = r.headers.get('content-type') or 'image/jpg' mime = r.headers.get('content-type') or 'image/jpg'
data = io.BytesIO(r.content).read() data = io.BytesIO(r.content).read()
uri = datauri.DataURI.make(mime,charset='ascii',base64=True,data=data) #uri = datauri.DataURI.make(mime,charset='ascii',base64=True,data=data)
log(f"Downloaded {url} for local caching") targetname = '%030x' % random.getrandbits(128)
return uri targetpath = data_dir['cache']('images',targetname)
with open(targetpath,'wb') as fd:
fd.write(data)
return os.path.join("cacheimages",targetname)
except Exception: except Exception:
log(f"Image {url} could not be downloaded for local caching") log(f"Image {url} could not be downloaded for local caching")
return None return None
@ -206,8 +227,8 @@ def resolve_image(artist_id=None,track_id=None,album_id=None):
if len(images) != 0: if len(images) != 0:
result = random.choice(images) result = random.choice(images)
result = urllib.parse.quote(result) result = urllib.parse.quote(result)
result = {'type':'url','value':result} result = {'type':'localurl','value':result}
set_image_in_cache(artist_id or track_id or album_id,table,result['value']) set_image_in_cache(artist_id or track_id or album_id,table,result['value'],local=True)
return result return result
# third party # third party
@ -236,7 +257,7 @@ def image_request(artist_id=None,track_id=None,album_id=None):
# use placeholder # use placeholder
placeholder_url = "https://generative-placeholders.glitch.me/image?width=300&height=300&style=" placeholder_url = "https://generative-placeholders.glitch.me/image?width=300&height=300&style="
if artist_id: if artist_id:
result['value'] = placeholder_url + f"123&colors={artist_id % 100}" result['value'] = placeholder_url + f"tiles&colors={artist_id % 100}"
if track_id: if track_id:
result['value'] = placeholder_url + f"triangles&colors={track_id % 100}" result['value'] = placeholder_url + f"triangles&colors={track_id % 100}"
if album_id: if album_id:
@ -377,6 +398,6 @@ def set_image(b64,**keys):
log("Saved image as " + data_dir['images'](folder,filename),module="debug") log("Saved image as " + data_dir['images'](folder,filename),module="debug")
# set as current picture in rotation # set as current picture in rotation
set_image_in_cache(id,dbtable,os.path.join("/images",folder,filename)) set_image_in_cache(id,dbtable,os.path.join("/images",folder,filename),local=True)
return os.path.join("/images",folder,filename) return os.path.join("/images",folder,filename)

View File

@ -128,13 +128,7 @@ def dynamic_image():
response.status = 503 response.status = 503
response.set_header('Retry-After',5) response.set_header('Retry-After',5)
return return
if result['type'] == 'raw': if result['type'] in ('url','localurl'):
# data uris are directly served as image because a redirect to a data uri
# doesnt work
duri = datauri.DataURI(result['value'])
response.content_type = duri.mimetype
return duri.data
if result['type'] == 'url':
redirect(result['value'],307) redirect(result['value'],307)
@webserver.route("/images/<pth:re:.*\\.jpeg>") @webserver.route("/images/<pth:re:.*\\.jpeg>")
@ -161,6 +155,9 @@ def static_image(pth):
resp.set_header("Content-Type", "image/" + ext) resp.set_header("Content-Type", "image/" + ext)
return resp return resp
@webserver.route("/cacheimages/<uuid>")
def static_proxied_image(uuid):
return static_file(uuid,root=data_dir['cache']('images'))
@webserver.route("/login") @webserver.route("/login")
def login(): def login():