Start to cache actor icon
This commit is contained in:
parent
8556cd29ef
commit
13c63e473a
5 changed files with 99 additions and 1 deletions
44
app.py
44
app.py
|
@ -10,6 +10,7 @@ from datetime import timezone
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from typing import Tuple
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ from config import BASE_URL
|
||||||
from config import DB
|
from config import DB
|
||||||
from config import DEBUG_MODE
|
from config import DEBUG_MODE
|
||||||
from config import DOMAIN
|
from config import DOMAIN
|
||||||
|
from config import GRIDFS
|
||||||
from config import HEADERS
|
from config import HEADERS
|
||||||
from config import ICON_URL
|
from config import ICON_URL
|
||||||
from config import ID
|
from config import ID
|
||||||
|
@ -69,9 +71,12 @@ from little_boxes.httpsig import HTTPSigAuth
|
||||||
from little_boxes.httpsig import verify_request
|
from little_boxes.httpsig import verify_request
|
||||||
from little_boxes.webfinger import get_actor_url
|
from little_boxes.webfinger import get_actor_url
|
||||||
from little_boxes.webfinger import get_remote_follow_template
|
from little_boxes.webfinger import get_remote_follow_template
|
||||||
|
from utils.img import ImageCache
|
||||||
from utils.key import get_secret_key
|
from utils.key import get_secret_key
|
||||||
from utils.object_service import ObjectService
|
from utils.object_service import ObjectService
|
||||||
|
|
||||||
|
IMAGE_CACHE = ImageCache(GRIDFS)
|
||||||
|
|
||||||
OBJECT_SERVICE = ACTOR_SERVICE = ObjectService()
|
OBJECT_SERVICE = ACTOR_SERVICE = ObjectService()
|
||||||
|
|
||||||
back = activitypub.MicroblogPubBackend()
|
back = activitypub.MicroblogPubBackend()
|
||||||
|
@ -180,6 +185,30 @@ def clean_html(html):
|
||||||
return bleach.clean(html, tags=ALLOWED_TAGS)
|
return bleach.clean(html, tags=ALLOWED_TAGS)
|
||||||
|
|
||||||
|
|
||||||
|
_GRIDFS_CACHE: Dict[Tuple[str, int], str] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_actor_icon_url(url, size):
|
||||||
|
k = (url, size)
|
||||||
|
cached = _GRIDFS_CACHE.get(k)
|
||||||
|
if cached:
|
||||||
|
return cached
|
||||||
|
|
||||||
|
doc = IMAGE_CACHE.fs.find_one({"url": url, "size": size})
|
||||||
|
if doc:
|
||||||
|
u = f"/img/{str(doc._id)}"
|
||||||
|
_GRIDFS_CACHE[k] = u
|
||||||
|
return u
|
||||||
|
|
||||||
|
IMAGE_CACHE.cache_actor_icon(url)
|
||||||
|
return _get_actor_icon_url(url, size)
|
||||||
|
|
||||||
|
|
||||||
|
@app.template_filter()
|
||||||
|
def get_actor_icon_url(url, size):
|
||||||
|
return _get_actor_icon_url(url, size)
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter()
|
@app.template_filter()
|
||||||
def permalink_id(val):
|
def permalink_id(val):
|
||||||
return str(hash(val))
|
return str(hash(val))
|
||||||
|
@ -357,6 +386,21 @@ def handle_activitypub_error(error):
|
||||||
|
|
||||||
# App routes
|
# App routes
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/img/<img_id>")
|
||||||
|
def serve_img(img_id):
|
||||||
|
f = IMAGE_CACHE.fs.get(ObjectId(img_id))
|
||||||
|
resp = app.response_class(f, direct_passthrough=True, mimetype=f.content_type)
|
||||||
|
resp.headers.set("Content-Length", f.length)
|
||||||
|
resp.headers.set("ETag", f.md5)
|
||||||
|
resp.headers.set(
|
||||||
|
"Last-Modified", f.uploadDate.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||||
|
)
|
||||||
|
resp.headers.set("Cache-Control", "public,max-age=31536000,immutable")
|
||||||
|
resp.headers.set("Content-Encoding", "gzip")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
#######
|
#######
|
||||||
# Login
|
# Login
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@ mongo_client = MongoClient(
|
||||||
|
|
||||||
DB_NAME = "{}_{}".format(USERNAME, DOMAIN.replace(".", "_"))
|
DB_NAME = "{}_{}".format(USERNAME, DOMAIN.replace(".", "_"))
|
||||||
DB = mongo_client[DB_NAME]
|
DB = mongo_client[DB_NAME]
|
||||||
|
GRIDFS = mongo_client[f"{DB_NAME}_gridfs"]
|
||||||
|
|
||||||
|
|
||||||
def _drop_db():
|
def _drop_db():
|
||||||
|
|
|
@ -19,3 +19,4 @@ passlib
|
||||||
git+https://github.com/erikriver/opengraph.git
|
git+https://github.com/erikriver/opengraph.git
|
||||||
git+https://github.com/tsileo/little-boxes.git
|
git+https://github.com/tsileo/little-boxes.git
|
||||||
pyyaml
|
pyyaml
|
||||||
|
pillow
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<div class="note h-entry" id="activity-{{ obj.id | permalink_id }}">
|
<div class="note h-entry" id="activity-{{ obj.id | permalink_id }}">
|
||||||
|
|
||||||
<div class="h-card p-author">
|
<div class="h-card p-author">
|
||||||
<a class="u-url u-uid no-hover" href="{{ actor.url | get_url }}"><img class="u-photo" src="{% if not actor.icon %}/static/nopic.png{% else %}{{ actor.icon.url }}{% endif %}">
|
<a class="u-url u-uid no-hover" href="{{ actor.url | get_url }}"><img class="u-photo" src="{% if not actor.icon %}/static/nopic.png{% else %}{{ actor.icon.url | get_actor_icon_url(50) }}{% endif %}">
|
||||||
</a>
|
</a>
|
||||||
<data class="p-name" value="{{ actor.name or actor.preferredUsername }}"></data>
|
<data class="p-name" value="{{ actor.name or actor.preferredUsername }}"></data>
|
||||||
</div>
|
</div>
|
||||||
|
|
52
utils/img.py
Normal file
52
utils/img.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import base64
|
||||||
|
from gzip import GzipFile
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import gridfs
|
||||||
|
import requests
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
def load(url):
|
||||||
|
"""Initializes a `PIL.Image` from the URL."""
|
||||||
|
# TODO(tsileo): user agent
|
||||||
|
resp = requests.get(url, stream=True)
|
||||||
|
resp.raise_for_status()
|
||||||
|
try:
|
||||||
|
image = Image.open(BytesIO(resp.raw.read()))
|
||||||
|
finally:
|
||||||
|
resp.close()
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
def to_data_uri(img):
|
||||||
|
out = BytesIO()
|
||||||
|
img.save(out, format=img.format)
|
||||||
|
out.seek(0)
|
||||||
|
data = base64.b64encode(out.read()).decode("utf-8")
|
||||||
|
return f"data:{img.get_format_mimetype()};base64,{data}"
|
||||||
|
|
||||||
|
|
||||||
|
class ImageCache(object):
|
||||||
|
def __init__(self, gridfs_db: str) -> None:
|
||||||
|
self.fs = gridfs.GridFS(gridfs_db)
|
||||||
|
|
||||||
|
def cache_actor_icon(self, url: str):
|
||||||
|
if self.fs.find_one({"url": url}):
|
||||||
|
return
|
||||||
|
i = load(url)
|
||||||
|
for size in [50, 80]:
|
||||||
|
t1 = i.copy()
|
||||||
|
t1.thumbnail((size, size))
|
||||||
|
with BytesIO() as buf:
|
||||||
|
f1 = GzipFile(mode='wb', fileobj=buf)
|
||||||
|
t1.save(f1, format=i.format)
|
||||||
|
f1.close()
|
||||||
|
buf.seek(0)
|
||||||
|
self.fs.put(
|
||||||
|
buf, url=url, size=size, content_type=i.get_format_mimetype()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_file(self, url: str, size: int) -> Any:
|
||||||
|
return self.fs.find_one({"url": url, "size": size})
|
Loading…
Reference in a new issue