diff --git a/app/svganim/strokes.py b/app/svganim/strokes.py index c51829e..50796bf 100644 --- a/app/svganim/strokes.py +++ b/app/svganim/strokes.py @@ -522,6 +522,8 @@ class AnnotationIndex: Drawing(fn, self.metadata_dir, self.drawing_dir) for fn in self.get_drawing_filenames() ] } + + self.root_tag = getRootTag() self.shelve['_tags'] = {tag.id: [] for tag in self.root_tag.descendants} self.shelve['_annotations'] = {} diff --git a/app/webserver.py b/app/webserver.py index 8644638..243e0d4 100644 --- a/app/webserver.py +++ b/app/webserver.py @@ -2,6 +2,7 @@ import json import logging import os import shutil +from urllib.error import HTTPError import tornado.ioloop import tornado.web import tornado.websocket @@ -12,6 +13,7 @@ import html import argparse import coloredlogs import glob +import filelock import svganim.strokes import svganim.uimethods @@ -410,6 +412,41 @@ class AnnotationHandler(tornado.web.RequestHandler): "tag": annotation.tag, "audio": f"/annotation/{annotation.id}.mp3", })) + + def post(self, annotation_id): + """change tag for given annotation""" + if annotation_id not in self.index.annotations: + raise tornado.web.HTTPError(404) + + # might be set on file level, but let's try to avoid issues by keeping it simple + lock = filelock.FileLock("metadata_write.lock", timeout=10) + with lock: + newTagId = self.get_argument('tag_id') + if not self.index.has_tag(newTagId): + raise tornado.web.HTTPError(400) + + annotation: svganim.strokes.Annotation = self.index.annotations[annotation_id] + + logger.info(f"change tag from {annotation.tag} to {newTagId}") + + # change metadata and reload index + metadata = annotation.drawing.get_metadata() + change = False + for idx, ann in enumerate(metadata['annotations']): + if ann['t_in'] == annotation.t_in and ann['t_out'] == annotation.t_out and annotation.tag == ann['tag']: + #found!? + metadata['annotations'][idx]['tag'] = newTagId + change = True + break + + if change == False: + raise HTTPError(409) + + with open(annotation.drawing.metadata_fn, "w") as fp: + logger.info(f"save tag in {annotation.drawing.metadata_fn}") + json.dump(metadata, fp) + + self.index.refresh() class DrawingHandler(tornado.web.RequestHandler): @@ -544,6 +581,9 @@ class TagsHandler(tornado.web.RequestHandler): with open('www/tags.json', 'w') as fp: fp.write(newTagsContent) + + # update as to load new tag into cache + self.index.refresh() self.set_status(204) # print() diff --git a/app/www/annotations.js b/app/www/annotations.js index 034b9cd..a3f1e7a 100644 --- a/app/www/annotations.js +++ b/app/www/annotations.js @@ -301,9 +301,23 @@ class AnnotationManager { this.saveTags(); } - moveSelectedAnnotations(tag) { + async moveSelectedAnnotations(tag) { // TODO: add button for this - alert(`This doesn't work yet! (move to tag ${tag.get_name()})`) + // alert(`This doesn't work yet! (move to tag ${tag.get_name()})`) + + await Promise.all(this.selectedAnnotations.map(async (annotation) => { + const formData = new FormData(); + formData.append('tag_id', tag.id) + return await fetch('/annotation/'+annotation.id, { + method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + body: formData //JSON.stringify({'tag_id': tag.id}) + }).catch((e) => alert('Something went wrong saving the tags')); + })); + + this.loadAnnotationsForTag(this.selectedTag) } async saveTags() { diff --git a/poetry.lock b/poetry.lock index 2243d72..2b67af8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,7 +2,7 @@ name = "anytree" version = "2.8.0" description = "Powerful and Lightweight Python Tree Data Structure.." -category = "main" +category = "dev" optional = false python-versions = "*" @@ -27,6 +27,18 @@ humanfriendly = ">=9.1" [package.extras] cron = ["capturer (>=2.4)"] +[[package]] +name = "filelock" +version = "3.7.1" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + [[package]] name = "humanfriendly" version = "10.0" @@ -58,7 +70,7 @@ python-versions = "*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" @@ -81,7 +93,7 @@ python-versions = ">= 3.5" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "1ad87cd5b37157a20ee2ec8f23d84b0275ca9f3aa3218c98eb18859d43aa5080" +content-hash = "11298c8670e03d4235a76555b1ca7e6cbf0740041f14c14fdf409f633e197bf5" [metadata.files] anytree = [ @@ -92,6 +104,10 @@ coloredlogs = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, ] +filelock = [ + {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, + {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, +] humanfriendly = [ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, diff --git a/pyproject.toml b/pyproject.toml index b6846fc..7267a38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ coloredlogs = "^15.0.1" pydub = "^0.25.1" svgwrite = "^1.4.1" anytree = "^2.8.0" +filelock = "^3.7.1" [tool.poetry.dev-dependencies]