162 lines
6.1 KiB
Python
162 lines
6.1 KiB
Python
import os
|
|
import json
|
|
import argparse
|
|
import requests
|
|
from requests.exceptions import HTTPError
|
|
from photoapp.utils import get_extension, shasum
|
|
from photoapp.types import known_extensions
|
|
from photoapp.common import pwhash
|
|
from photoapp.ingest import get_photosets
|
|
|
|
|
|
class PhotoApiClient(object):
|
|
def __init__(self, base_url, passwd=None):
|
|
self.session = requests.Session()
|
|
if passwd:
|
|
self.session.auth = passwd # user, pass tuple
|
|
self.base_url = base_url
|
|
|
|
def byhash(self, sha):
|
|
return self.get("byhash", params={"sha": sha}).json()
|
|
|
|
def get(self, url, **params):
|
|
return self.do("get", url, **params)
|
|
|
|
def post(self, url, **params):
|
|
return self.do("post", url, **params)
|
|
|
|
def delete(self, url, **params):
|
|
return self.do("delete", url, **params)
|
|
|
|
def do(self, method, url, **kwargs):
|
|
resp = getattr(self.session, method)(self.base_url + "/api/v1/" + url, **kwargs)
|
|
resp.raise_for_status()
|
|
return resp
|
|
|
|
def create_user(self, username, password):
|
|
return self.post("user", data={"username": username,
|
|
"password_hash": pwhash(password)})
|
|
|
|
def list_users(self):
|
|
return self.get("user")
|
|
|
|
def delete_user(self, username):
|
|
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 get_args():
|
|
parser = argparse.ArgumentParser(description="photo library cli")
|
|
# TODO nicer uri parser
|
|
parser.add_argument("--host", required=True, help="photo library server address")
|
|
parser.add_argument("--user", required=True)
|
|
parser.add_argument("--password", required=True)
|
|
|
|
sp_action = parser.add_subparsers(dest="action", help="action to take")
|
|
|
|
p_dupes = sp_action.add_parser("checkdupes", help="check if files/hash lists are already in the library")
|
|
p_dupes.add_argument("--sha-files", action="store_true", help="read hashes from a file instead of hashing images")
|
|
p_dupes.add_argument("files", nargs="+", help="files to check")
|
|
|
|
p_ingest = sp_action.add_parser("ingest", help="import images into the library")
|
|
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")
|
|
|
|
# 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")
|
|
|
|
p_create = p_useraction.add_parser('create', help='create user')
|
|
p_create.add_argument("-u", "--username", help="username", required=True)
|
|
p_create.add_argument("-p", "--password", help="password", required=True)
|
|
|
|
p_useraction.add_parser('list', help='list users')
|
|
|
|
p_delete = p_useraction.add_parser('delete', help='delete users')
|
|
p_delete.add_argument("-u", "--username", help="username", required=True)
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = get_args()
|
|
print(args)
|
|
|
|
client = PhotoApiClient(args.host, (args.user, args.password, ))
|
|
|
|
if args.action == "checkdupes":
|
|
hashes = {}
|
|
if args.sha_files:
|
|
# sha file format should look like the output of `find ./source -type f -exec shasum -a 256 {} \;`:
|
|
# e931d286a8377ea8f49ee928e793d9e1fd18a25402cac43afdee3eec3b3b18a5 source/2018-12-26 20.54.40.jpg
|
|
# 24c1cd20c961839f14d608f5719c4f380af902c4774ddb7cb9ff747f652ca302 source/2018-12-30 22.39.27.jpg
|
|
for fname in args.files:
|
|
with open(fname) as f:
|
|
for line in f.readlines():
|
|
if not line:
|
|
continue
|
|
sha, path = line.split(maxsplit=1)
|
|
path = path.rstrip("\n")
|
|
if get_extension(path) not in known_extensions:
|
|
continue
|
|
hashes[sha] = path
|
|
else:
|
|
raise NotImplementedError("must pass --sha-files for now")
|
|
|
|
for sha, path in hashes.items():
|
|
# hit http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6
|
|
try:
|
|
client.byhash(sha)
|
|
# if the file is a dupe, do nothing
|
|
except HTTPError as he:
|
|
# if the file is original, print its path
|
|
if he.response.status_code == 404:
|
|
print(path)
|
|
else:
|
|
raise
|
|
|
|
elif args.action == "ingest":
|
|
sets, skipped = get_photosets(args.files)
|
|
#TODO y/n confirmation and auto flag
|
|
#TODO optional progress printing
|
|
print("skipping:", skipped)
|
|
print("sets:", [[f.path for f in s.files] for s in sets])
|
|
|
|
for num, set_ in enumerate(sets):
|
|
payload = set_.to_json()
|
|
payload["files"] = {os.path.basename(photo.path): photo.to_json() for photo in set_.files}
|
|
|
|
files = []
|
|
for file in set_.files:
|
|
files.append(("files", (os.path.basename(file.path), open(file.path, 'rb'), file.format), ))
|
|
|
|
if args.copy_of:
|
|
payload["uuid"] = args.copy_of
|
|
|
|
print("Uploading: ", [os.path.basename(file.path) for file in set_.files])
|
|
try:
|
|
result = client.upload(files, payload)
|
|
print("Uploaded: ", result.json()["uuid"])
|
|
except HTTPError as he:
|
|
print(he.response.json())
|
|
# TODO collect errors and print later
|
|
# return
|
|
print(f"{num} / {len(sets)}")
|
|
# TODO be nice and close the files
|
|
|
|
elif args.action == "user":
|
|
if args.action_user == "create":
|
|
print(client.create_user(args.username, args.password).json())
|
|
elif args.action_user == "list":
|
|
for u in client.list_users().json():
|
|
print(f"{u['name']}: {u['status']}")
|
|
elif args.action_user == "delete":
|
|
print(client.delete_user(args.username).json())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|