listing apis
parent
2b5ab1fcc4
commit
bc64acce2a
|
@ -1,21 +1,11 @@
|
|||
import os
|
||||
import cherrypy
|
||||
import logging
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from photoapp.library import PhotoLibrary
|
||||
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User, known_extensions, known_mimes, genuuid
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import func, and_, or_
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from photoapp.common import pwhash
|
||||
import math
|
||||
from urllib.parse import urlparse
|
||||
from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify, copysha, get_extension
|
||||
from datetime import datetime
|
||||
from photoapp.types import Photo, PhotoSet, Tag, PhotoStatus, User, known_extensions, known_mimes, genuuid
|
||||
from photoapp.utils import copysha, get_extension
|
||||
from photoapp.image import special_magic_fobj
|
||||
from photoapp.dbutils import db
|
||||
import tempfile
|
||||
from contextlib import closing
|
||||
import traceback
|
||||
|
||||
|
@ -95,41 +85,6 @@ class LibraryManager(object):
|
|||
assert isinstance(storage, StorageAdapter)
|
||||
self.storage = storage
|
||||
|
||||
# def add_photoset(self, photoset):
|
||||
# """
|
||||
# Commit a populated photoset object to the library. The paths in the photoset's file list entries will be updated
|
||||
# as the file is moved to the library path.
|
||||
# """
|
||||
# # Create target directory
|
||||
# path = os.path.join(self.path, self.get_datedir_path(photoset.date))
|
||||
# os.makedirs(path, exist_ok=True)
|
||||
|
||||
# moves = [] # Track files moved. If the sql transaction files, we'll undo these
|
||||
|
||||
# for file in photoset.files:
|
||||
# dest = os.path.join(path, os.path.basename(file.path))
|
||||
|
||||
# # Check if the name is already in use, rename new file if needed
|
||||
# dupe_rename = 1
|
||||
# while os.path.exists(dest):
|
||||
# fname = os.path.basename(file.path).split(".")
|
||||
# fname[-2] += "_{}".format(dupe_rename)
|
||||
# dest = os.path.join(path, '.'.join(fname))
|
||||
# dupe_rename += 1
|
||||
# os.rename(file.path, dest)
|
||||
# moves.append((file.path, dest))
|
||||
# file.path = dest.lstrip(self.path)
|
||||
|
||||
# s = self.session()
|
||||
# s.add(photoset)
|
||||
# try:
|
||||
# s.commit()
|
||||
# except IntegrityError:
|
||||
# # Commit failed, undo the moves
|
||||
# for move in moves:
|
||||
# os.rename(move[1], move[0])
|
||||
# raise
|
||||
|
||||
|
||||
class PhotosApi(object):
|
||||
def __init__(self, library):
|
||||
|
@ -290,3 +245,27 @@ class PhotosApiV1(object):
|
|||
else:
|
||||
db.add(User(name=username, password=password_hash))
|
||||
return "ok"
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def stats(self):
|
||||
return {"photos": db.query(PhotoSet).count(),
|
||||
"files": db.query(Photo).count(),
|
||||
"tags": db.query(Tag).count(),
|
||||
"users": db.query(User).count(),
|
||||
"public_photos": db.query(PhotoSet).filter(PhotoSet.status == PhotoStatus.public).count()}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@cherrypy.popargs("uuid")
|
||||
def photos(self, uuid=None, page=0, pagesize=50):
|
||||
if uuid:
|
||||
p = db.query(PhotoSet).filter(PhotoSet.uuid == uuid).first()
|
||||
if not p:
|
||||
cherrypy.response.status = 404
|
||||
return {"error": "not found"}
|
||||
return p.to_json()
|
||||
else:
|
||||
page, pagesize = int(page), int(pagesize)
|
||||
return [p.to_json() for p in
|
||||
db.query(PhotoSet).order_by(PhotoSet.id).offset(pagesize * page).limit(pagesize).all()]
|
||||
|
|
|
@ -44,9 +44,20 @@ class PhotoApiClient(object):
|
|||
return self.delete("user", params={"username": username})
|
||||
|
||||
def upload(self, files, metadata):
|
||||
# print(">>>>>>", metadata)
|
||||
return self.post("upload", files=files, data={"meta": json.dumps(metadata)})
|
||||
|
||||
def stats(self):
|
||||
return self.get("stats").json()
|
||||
|
||||
def list_photos(self, page=0, pagesize=25):
|
||||
return self.get("photos", params={"page": page, "pagesize": pagesize}).json()
|
||||
|
||||
|
||||
def maybetruncate(s, length):
|
||||
if s and len(s) > length:
|
||||
s = s[0:length - 3] + "..."
|
||||
return s
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser(description="photo library cli")
|
||||
|
@ -65,6 +76,13 @@ def get_args():
|
|||
p_ingest.add_argument("-c", "--copy-of", help="existing uuid the imported images will be placed under")
|
||||
p_ingest.add_argument("files", nargs="+", help="files to import")
|
||||
|
||||
sp_action.add_parser("stats", help="show library statistics")
|
||||
|
||||
p_list = sp_action.add_parser("list", help="list images in library")
|
||||
p_list.add_argument("-p", "--page", default=0, help="page number")
|
||||
p_list.add_argument("-z", "--page-size", default=25, help="page size")
|
||||
p_list.add_argument("-l", "--show-link", action="store_true", help="print urls to each photo")
|
||||
|
||||
# User section
|
||||
p_adduser = sp_action.add_parser("user", help="user manipulation functions")
|
||||
p_useraction = p_adduser.add_subparsers(dest="action_user", help="action to take")
|
||||
|
@ -83,7 +101,6 @@ def get_args():
|
|||
|
||||
def main():
|
||||
args = get_args()
|
||||
print(args)
|
||||
|
||||
client = PhotoApiClient(args.host, (args.user, args.password, ))
|
||||
|
||||
|
@ -107,7 +124,7 @@ def main():
|
|||
raise NotImplementedError("must pass --sha-files for now")
|
||||
|
||||
for sha, path in hashes.items():
|
||||
# hit http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6
|
||||
# http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6
|
||||
try:
|
||||
client.byhash(sha)
|
||||
# if the file is a dupe, do nothing
|
||||
|
@ -147,6 +164,30 @@ def main():
|
|||
print(f"{num} / {len(sets)}")
|
||||
# TODO be nice and close the files
|
||||
|
||||
elif args.action == "list":
|
||||
headers = ["link" if args.show_link else "uuid",
|
||||
"status",
|
||||
"size",
|
||||
"formats",
|
||||
"date",
|
||||
"title"]# TODO tags
|
||||
rows = []
|
||||
for set_ in client.list_photos(args.page, args.page_size):
|
||||
row = [set_["uuid"] if not args.show_link else args.host.rstrip("/") + "/photo/" + set_["uuid"],
|
||||
PhotoStatus[set_["status"]].name,
|
||||
sum([p["size"] for p in set_["files"].values()]),
|
||||
" ".join(set([p["format"] for p in set_["files"].values()])),
|
||||
set_["date"],
|
||||
maybetruncate(set_["title"], 24),
|
||||
]
|
||||
rows.append(row)
|
||||
print(tabulate(rows, headers=headers))
|
||||
|
||||
elif args.action == "stats":
|
||||
print(tabulate(sorted([[k, v] for k, v in client.stats().items()],
|
||||
key=lambda row: row[0], reverse=True),
|
||||
headers=["item", "count"]))
|
||||
|
||||
elif args.action == "user":
|
||||
if args.action_user == "create":
|
||||
print(client.create_user(args.username, args.password).json())
|
||||
|
|
|
@ -263,7 +263,6 @@ class ThumbnailView(object):
|
|||
best = photo
|
||||
break
|
||||
thumb_from = best or first
|
||||
print(repr(thumb_from))
|
||||
if not thumb_from:
|
||||
raise cherrypy.HTTPError(404)
|
||||
# TODO some lock around calls to this based on uuid
|
||||
|
|
|
@ -66,13 +66,15 @@ class PhotoSet(Base):
|
|||
|
||||
status = Column(Enum(PhotoStatus), default=PhotoStatus.private)
|
||||
|
||||
def to_json(self):
|
||||
def to_json(self, files=True):
|
||||
s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}}
|
||||
s["lat"] = str(self.lat) if self.lat else None
|
||||
s["lon"] = str(self.lon) if self.lon else None
|
||||
s["date"] = self.date.isoformat()
|
||||
s["files"] = {i.uuid: i.to_json() for i in self.files}
|
||||
s["tags"] = [t.name for t in self.tags]
|
||||
if files:
|
||||
s["files"] = {i.uuid: i.to_json() for i in self.files}
|
||||
s["tags"] = [t.tag.name for t in self.tags]
|
||||
s["status"] = self.status.name if self.status else PhotoStatus.private.name # duplicate default definition
|
||||
return s
|
||||
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ def main():
|
|||
p_create.add_argument("-u", "--username", help="username", required=True)
|
||||
p_create.add_argument("-p", "--password", help="password", required=True)
|
||||
|
||||
p_list = p_mode.add_parser('list', help='list users')
|
||||
p_mode.add_parser('list', help='list users')
|
||||
|
||||
p_delete = p_mode.add_parser('delete', help='delete users')
|
||||
p_delete.add_argument("-u", "--username", help="username", required=True)
|
||||
|
|
Loading…
Reference in New Issue