PK!O%$ $ imagedb/__init__.pyfrom flask import Flask from threading import Thread import re from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from . import db from .util import open_browser_tab app = Flask(__name__) config = dict() class ImageDB: def __init__(self, db_path, host='localhost', port='8000', debug=False): global config config['image_db'] = self self.db_folder = os.path.splitext(db_path)[0] self.engine = create_engine('sqlite:///' + db_path, connect_args={'check_same_thread': False}) Session = sessionmaker(bind=self.engine) self.session = Session() if not os.path.exists(db_path): db.Base.metadata.create_all(self.engine) self._run_server(host, port, debug) @staticmethod def _run_server(host='localhost', port='8000', debug=False): open_browser_tab('http://{}:{}'.format(host, port)) app_thread = Thread(target=app.run, kwargs=dict( host=host, port=port, debug=debug )) app_thread.daemon = True app_thread.start() def search(self, content=None, tag_or_tags=None, type_='partial'): def _compare(text, text_compare): if type_ == 'partial': return text_compare in text elif type_ in {'regex', 'regexp', 're'}: return re.search(text_compare, text, flags=re.IGNORECASE) else: return text_compare == text def _filter_tag(text_compare): for db_image in query: if any(_compare(tic.tag.name, text_compare) for tic in db_image.tags): yield db_image def _filter_slide(text_compare): for db_card in query: if any(_compare(db_image.info, text_compare) for db_image in query if db_image.info): yield db_card query = iter(self.session.query(db.Image).order_by(db.Image.modified.desc())) if tag_or_tags: if isinstance(tag_or_tags, str): query = _filter_tag(tag_or_tags) else: for tag in tag_or_tags: query = _filter_tag(tag) if content: query = _filter_slide(content) return list(query) from .views import * from .api import * PK!C 4 4 imagedb/api.pyfrom flask import request, jsonify, Response from werkzeug.utils import secure_filename from pathlib import Path from nonrepeat import nonrepeat_filename from slugify import slugify from uuid import uuid4 from . import app, db, config filename = None @app.route('/api/images/create', methods=['POST']) def create_image(): global filename if 'file' in request.files: image_path = Path(config['image_db'].db_folder) tags = request.form.get('tags') if image_path.suffix: image_path = image_path.parent file = request.files['file'] if file.filename == 'image.png': filename = 'blob/' + str(uuid4())[:8] + '.png' image_path.joinpath('blob').mkdir(parents=True, exist_ok=True) else: filename = secure_filename(file.filename) image_path.mkdir(parents=True, exist_ok=True) filename = str(image_path.joinpath(filename) .relative_to(config['image_db'].db_folder)) filename = nonrepeat_filename(filename, primary_suffix=slugify('-'.join(tags)), root=str(image_path)) db_image = db.Image() db_image.filename = filename filename = db_image.filename db_image.add_tags(tags) true_filename = str(image_path.joinpath(filename)) file.save(true_filename) return jsonify({ 'filename': filename, 'trueFilename': true_filename }), 201 return Response(status=304) @app.route('/api/images/rename', methods=['POST']) def rename_image(): global filename db_image = config['image_db'].session.query(db.Image).filter_by(_filename=filename).first() if filename is not None and db_image is not None: post_json = request.get_json() db_image.add_tags(post_json['tags']) config['image_db'].session.commit() new_filename = str(Path(post_json['filename']).with_suffix(Path(filename).suffix)) db_image.filename = new_filename filename = db_image.filename true_filename = str(Path(config['image_db'].db_folder).joinpath(filename)) return jsonify({ 'filename': filename, 'trueFilename': true_filename }), 201 return Response(status=304) PK!P imagedb/db.pyfrom datetime import datetime from pathlib import Path import shutil from nonrepeat import nonrepeat_filename from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, select from sqlalchemy.orm import relationship, deferred Base = declarative_base() class Image(Base): __tablename__ = 'image' id = Column(Integer, primary_key=True, autoincrement=True) _filename = Column(String, nullable=False, unique=True) info = Column(String, nullable=True, unique=True) created = Column(DateTime, default=datetime.now) modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) _tags = relationship('TagImageConnect', order_by='TagImageConnect.tag_name', back_populates='image') def to_json(self): return { 'id': self.id, 'filename': self.filename, 'info': self.info, 'created': self.created.isoformat(), 'modified': self.modified.isoformat(), 'tags': self.tags } def to_html(self): from . import config return ''.format(str(Path(config['image_db'].db_folder).joinpath(self.filename))) def _repr_html_(self): return self.to_html() def add_tags(self, tag_or_tags='marked'): from . import config def _mark(tag): db_tag = config['image_db'].session.query(Tag).filter_by(name=tag).first() if db_tag is None: db_tag = Tag() db_tag.name = tag config['image_db'].session.add(db_tag) config['image_db'].session.commit() db_tic = config['image_db'].session.query(TagImageConnect).filter_by(tag_id=db_tag.id, image_id=self.id).first() if db_tic is None: db_tic = TagImageConnect() db_tic.tag_id = db_tag.id db_tic.card_id = db_tic.id config['image_db'].session.add(db_tic) config['image_db'].session.commit() else: pass # raise ValueError('The card is already marked by "{}".'.format(tag)) return db_tag if isinstance(tag_or_tags, str): yield _mark(tag_or_tags) else: for x in tag_or_tags: yield _mark(x) def remove_tags(self, tag_or_tags='marked'): from . import config def _unmark(tag): db_tag = config['image_db'].session.query(Tag).filter_by(name=tag).first() if db_tag is None: raise ValueError('Cannot unmark "{}"'.format(tag)) # return db_tic = config['image_db'].session.query(TagImageConnect).filter_by(tag_id=db_tag.id, card_id=self.id).first() if db_tic is None: raise ValueError('Cannot unmark "{}"'.format(tag)) # return else: config['image_db'].session.delete(db_tic) config['image_db'].session.commit() yield db_tag if isinstance(tag_or_tags, str): yield _unmark(tag_or_tags) else: for x in tag_or_tags: yield _unmark(x) @property def filename(self): return self._filename @filename.setter def filename(self, new_filename): from . import config if self.filename: new_filename = Path(new_filename) new_filename = new_filename\ .with_suffix(Path(self._filename).suffix)\ .with_name(new_filename.name) new_filename = nonrepeat_filename(str(new_filename), primary_suffix='-'.join(self.tags), root=config['image_db'].db_folder) true_filename = Path(config['image_db'].db_folder).joinpath(new_filename) true_filename.parent.mkdir(parents=True, exist_ok=True) true_filename = str(true_filename) shutil.move(str(Path(config['image_db'].db_folder).joinpath(self._filename)), true_filename) self._filename = new_filename else: self._filename = new_filename config['image_db'].session.add(self) config['image_db'].session.commit() @property def tags(self): return [tag.tag_name for tag in self._tags] def delete(self): from . import config config['image_db'].session.delete(self) Path(config['image_db'].db_folder).joinpath(self.filename).unlink() class Tag(Base): __tablename__ = 'tag' id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String, nullable=False) images = relationship('TagImageConnect', order_by='TagImageConnect.id', back_populates='tag') class TagImageConnect(Base): __tablename__ = 'tag_image_connect' id = Column(Integer, primary_key=True, autoincrement=True) image_id = Column(Integer, ForeignKey('image.id'), nullable=False) tag_id = Column(Integer, ForeignKey('tag.id'), nullable=False) image = relationship('Image', back_populates='_tags') tag = relationship('Tag', back_populates='images') tag_name = deferred(select([Tag.name]).where(Tag.id == tag_id)) PK!"imagedb/static/index.cssinput { font-size: 24px; margin: 0 auto; margin-top: 20px; } #tags-area, #input-bar { width: 500px; } #tags-bar { width: 100%; } img { margin-top: 20px; } PK!Q~n n imagedb/static/index.jsconst tagsBar = document.getElementById('tags-bar'); const inputBar = document.getElementById('input-bar'); const imageCanvas = document.getElementById('image-canvas'); let tags = []; let imagePath = ''; inputBar.value = imagePath; inputBar.onpaste = ()=>{ const items = (event.clipboardData || event.originalEvent.clipboardData).items; // console.log(items); // will give you the mime types for (index in items) { const item = items[index]; if (item.kind === 'file') { const file = item.getAsFile(); console.log(file); let reader = new FileReader(); reader.onload = function(event) { const extension = file.type.match(/\/([a-z0-9]+)/i)[1].toLowerCase(); let formData = new FormData(); formData.append('file', file, file.name); formData.append('extension', extension); formData.append('mimetype', file.type); formData.append('submission-type', 'paste'); // formData.append('imagePath', imagePath); formData.append('tags', tags); fetch('/api/images/create', { method: 'POST', body: formData }).then(response=>response.json()) .then(responseJson=>{ inputBar.value = responseJson.filename; imageCanvas.src = '/images?filename=' + encodeURIComponent(responseJson.trueFilename); }); }; reader.readAsBinaryString(file); } } } inputBar.addEventListener("keydown", function(event) { imagePath = inputBar.value; }); inputBar.addEventListener("keyup", function(event) { if (event.keyCode === 13) { fetch('/api/images/rename', { method: 'POST', headers: { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify({ 'filename': imagePath, 'tags': tags }) }).then(response=>response.json()) .then(responseJson=>{ inputBar.value = responseJson.filename; imageCanvas.src = '/images?filename=' + encodeURIComponent(responseJson.trueFilename); alert('Renaming successful!'); }); } }); tagsBar.addEventListener("keydown", function(event) { function purge(tag){ tag = tag.trim(); if(tag){ tags.push(tag); } } tags = []; let purgable = true; let currentTag = '' tagsBar.value.split('').forEach((character, index)=>{ if(character === ',' && purgable){ if(purgable){ purge(currentTag); currentTag = ''; } else { currentTag += character; } } else if (character === '\"'){ purgable = !purgable; } else { currentTag += character; } }); purge(currentTag); }) PK!Z imagedb/templates/index.html

PK!imagedb/util.pyfrom threading import Thread from time import sleep import webbrowser def open_browser_tab(url): def _open_tab(): sleep(1) webbrowser.open_new_tab(url) thread = Thread(target=_open_tab) thread.daemon = True thread.start() PK!x::imagedb/views.pyfrom flask import render_template, send_file, request import os from urllib.parse import unquote from . import app, config @app.route('/') def index(): return render_template('index.html') @app.route('/images') def get_image(): return send_file(os.path.abspath(unquote(request.args.get('filename')))) PK!H WXimagedb-0.1.3.dist-info/WHEEL A н#Z."jm)Afb~ڠO68oF04UhoAf f4=4h0k::wXPK!Hd imagedb-0.1.3.dist-info/METADATAN0E /AJ$* $J@TzN~NT)BUMvsz` <]B%'-n7\LغW Pug)pk)8RpۄcٓQڠz^*?w;JnyRT@tpDF! 3H|C/C>5:4&cyq3ژ6f9{}O}H+y|\dɢ9vhHO'L\"-Ii"Y.!oI[jcoq%z PK!HJy )imagedb-0.1.3.dist-info/RECORDuv@@} R h0 PTExiU{@ql;1) 5,a\#௕*z a> ,Kʼh$ O^g,2eXԛ)(X B 4T&bCT&6U#`)Rw.= xND"oyrYz~})jBs۪]+~ ) TVL0‹~R\Ndjrc1E, NGfwVLj ڶ%8y_v/6QjV:{Tmv* m-u_H^|2߰gPل dXDSTyHN}V /M  Č_ "G4x PK!O%$ $ imagedb/__init__.pyPK!C 4 4 U imagedb/api.pyPK!P imagedb/db.pyPK!"k(imagedb/static/index.cssPK!Q~n n K)imagedb/static/index.jsPK!Z 3imagedb/templates/index.htmlPK!6imagedb/util.pyPK!x::B7imagedb/views.pyPK!H WX8imagedb-0.1.3.dist-info/WHEELPK!Hd <9imagedb-0.1.3.dist-info/METADATAPK!HJy ):imagedb-0.1.3.dist-info/RECORDPK <