PK ! J J imagedb/__init__.pyfrom flask import Flask
import os
import re
from threading import Thread
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):
global config
config['image_db'] = self
self.db_folder = os.path.splitext(db_path)[0]
self.engine = create_engine('sqlite:///' + db_path)
Session = sessionmaker(bind=self.engine)
self.session = Session()
if not os.path.exists(db_path):
db.Base.metadata.create_all(self.engine)
@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 mark(self, db_image, tag_or_tags='marked'):
def _mark(tag):
db_tag = self.session.query(db.Tag).filter_by(name=tag).first()
if db_tag is None:
db_tag = db.Tag()
db_tag.name = tag
self.session.add(db_tag)
self.session.commit()
db_tic = self.session.query(db.TagImageConnect).filter_by(tag_id=db_tag.id,
image_id=db_image.id).first()
if db_tic is None:
db_tic = db.TagImageConnect()
db_tic.tag_id = db_tag.id
db_tic.card_id = db_tic.id
self.session.add(db_tic)
self.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 unmark(self, db_image, tag_or_tags='marked'):
def _unmark(tag):
db_tag = self.session.query(db.Tag).filter_by(name=tag).first()
if db_tag is None:
raise ValueError('Cannot unmark "{}"'.format(tag))
# return
db_tic = self.session.query(db.TagImageConnect).filter_by(tag_id=db_tag.id,
card_id=db_image.id).first()
if db_tic is None:
raise ValueError('Cannot unmark "{}"'.format(tag))
# return
else:
self.session.delete(db_tic)
self.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)
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 ! SgP P imagedb/api.pyfrom flask import request, jsonify, Response
from werkzeug.utils import secure_filename
from pathlib import Path
from nonrepeat import nonrepeat_filename
import shutil
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).joinpath(request.form.get('imagePath'))
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()) + '.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
config['image_db'].mark(db_image, tags)
config['image_db'].session.add(db_image)
config['image_db'].session.commit()
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()
new_filename = Path(post_json['filename'])\
.with_suffix(Path(filename).suffix)
new_filename.parent.mkdir(parents=True, exist_ok=True)
new_filename = new_filename.with_name(secure_filename(new_filename.name))
new_filename = nonrepeat_filename(str(new_filename),
root=config['image_db'].db_folder)
true_filename = str(Path(config['image_db'].db_folder).joinpath(new_filename))
shutil.move(str(Path(config['image_db'].db_folder).joinpath(filename)),
true_filename)
filename = new_filename
db_image.filename = filename
config['image_db'].mark(db_image, post_json['tags'])
config['image_db'].session.commit()
return jsonify({
'filename': filename,
'trueFilename': true_filename
}), 201
return Response(status=304)
PK ! RQ[ [
imagedb/db.pyfrom datetime import datetime
import os
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_html(self):
from . import config
return ''.format(os.path.join(config['image_db'].db_folder, self.filename))
def _repr_html_(self):
return self.to_html()
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 ! =k
k
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