pep8'd what I could, including tabs to spaces

This commit is contained in:
Markus Pawlata 2018-11-30 01:28:24 +01:00
parent a45ecc76b0
commit 48bf325d77
11 changed files with 1251 additions and 949 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ scanner/floatapp/app.cfg
*.pyc *.pyc
*.min.css *.min.css
*.min.js *.min.js
.vscode

View File

@ -1,49 +1,90 @@
import os.path import os.path
from datetime import datetime from datetime import datetime
def message(category, text): def message(category, text):
if message.level <= 0: if message.level <= 0:
sep = " " sep = " "
else: else:
sep = "--" sep = "--"
print "%s %s%s[%s]%s%s" % (datetime.now().isoformat(), max(0, message.level) * " |", sep, category, max(1, (14 - len(category))) * " ", text) print "%s %s%s[%s]%s%s" % (
datetime.now().isoformat(),
max(0, message.level) * " |",
sep,
category,
max(1, (14 - len(category))) * " ",
text)
message.level = -1 message.level = -1
def next_level(): def next_level():
message.level += 1 message.level += 1
def back_level(): def back_level():
message.level -= 1 message.level -= 1
def set_cache_path_base(base): def set_cache_path_base(base):
trim_base.base = base trim_base.base = base
def untrim_base(path): def untrim_base(path):
return os.path.join(trim_base.base, path) return os.path.join(trim_base.base, path)
def trim_base_custom(path, base): def trim_base_custom(path, base):
if path.startswith(base): if path.startswith(base):
path = path[len(base):] path = path[len(base):]
if path.startswith('/'): if path.startswith('/'):
path = path[1:] path = path[1:]
return path return path
def trim_base(path): def trim_base(path):
return trim_base_custom(path, trim_base.base) return trim_base_custom(path, trim_base.base)
def cache_base(path, filepath=False): def cache_base(path, filepath=False):
if len(path) == 0: if len(path) == 0:
return "root" return "root"
elif filepath and len(path.split(os.sep)) < 2: elif filepath and len(path.split(os.sep)) < 2:
path = "root-" + path path = "root-" + path
path = trim_base(path).replace('/', '-').replace(' ', '_').replace('(', '').replace('&', '').replace(',', '').replace(')', '').replace('#', '').replace('[', '').replace(']', '').replace('"', '').replace("'", '').replace('_-_', '-').lower() path = trim_base(path).replace(
while path.find("--") != -1: '/', '-').replace(
path = path.replace("--", "-") ' ', '_').replace(
while path.find("__") != -1: '(', '').replace(
path = path.replace("__", "_") '&', '').replace(
return path ',', '').replace(
')', '').replace(
'#', '').replace(
'[', '').replace(
']', '').replace(
'"', '').replace(
"'", '').replace(
'_-_', '-').lower()
while path.find("--") != -1:
path = path.replace("--", "-")
while path.find("__") != -1:
path = path.replace("__", "_")
return path
def json_cache(path): def json_cache(path):
return cache_base(path) + ".json" return cache_base(path) + ".json"
def image_cache(path, size, square=False): def image_cache(path, size, square=False):
if square: if square:
suffix = str(size) + "s" suffix = str(size) + "s"
else: else:
suffix = str(size) suffix = str(size)
return cache_base(path, True) + "_" + suffix + ".jpg" return cache_base(path, True) + "_" + suffix + ".jpg"
def video_cache(path): def video_cache(path):
return cache_base(path, True) + ".mp4" return cache_base(path, True) + ".mp4"
def file_mtime(path): def file_mtime(path):
return datetime.fromtimestamp(int(os.path.getmtime(path))) return datetime.fromtimestamp(int(os.path.getmtime(path)))

File diff suppressed because it is too large Load Diff

View File

@ -6,129 +6,143 @@ from PhotoAlbum import Photo, Album, PhotoAlbumEncoder
from CachePath import * from CachePath import *
import json import json
class TreeWalker:
def __init__(self, album_path, cache_path):
self.album_path = os.path.abspath(album_path).decode(sys.getfilesystemencoding())
self.cache_path = os.path.abspath(cache_path).decode(sys.getfilesystemencoding())
set_cache_path_base(self.album_path)
self.all_albums = list()
self.all_photos = list()
self.walk(self.album_path)
self.big_lists()
self.remove_stale()
message("complete", "")
def walk(self, path):
next_level()
if not os.access(path, os.R_OK | os.X_OK):
message("access denied", os.path.basename(path))
back_level()
return None
message("walking", os.path.basename(path))
cache = os.path.join(self.cache_path, json_cache(path))
cached = False
cached_album = None
if os.path.exists(cache):
try:
cached_album = Album.from_cache(cache)
if file_mtime(path) <= file_mtime(cache):
message("full cache", os.path.basename(path))
cached = True
album = cached_album
for photo in album.photos:
self.all_photos.append(photo)
else:
message("partial cache", os.path.basename(path))
except KeyboardInterrupt:
raise
except:
message("corrupt cache", os.path.basename(path))
cached_album = None
if not cached:
album = Album(path)
for entry in os.listdir(path):
if entry[0] == '.':
continue
try:
entry = entry.decode(sys.getfilesystemencoding())
except KeyboardInterrupt:
raise
except:
next_level()
message("unicode error", entry.decode(sys.getfilesystemencoding(), "replace"))
back_level()
continue
entry = os.path.join(path, entry)
if os.path.isdir(entry):
next_walked_album = self.walk(entry)
if next_walked_album is not None:
album.add_album(next_walked_album)
elif not cached and os.path.isfile(entry):
next_level()
cache_hit = False
if cached_album:
cached_photo = cached_album.photo_from_path(entry)
if cached_photo and file_mtime(entry) <= cached_photo.attributes["dateTimeFile"]:
cache_file = None
if "mediaType" in cached_photo.attributes:
if cached_photo.attributes["mediaType"] == "video":
# if video
cache_file = os.path.join(self.cache_path, video_cache(entry))
else:
# if image
cache_file = os.path.join(self.cache_path, image_cache(entry, 1024, False))
else:
# if image
cache_file = os.path.join(self.cache_path, image_cache(entry, 1024, False))
# at this point we have full path to cache image/video class TreeWalker:
# check if it actually exists def __init__(self, album_path, cache_path):
if os.path.exists(cache_file): self.album_path = os.path.abspath(
message("cache hit", os.path.basename(entry)) album_path).decode(sys.getfilesystemencoding())
cache_hit = True self.cache_path = os.path.abspath(
photo = cached_photo cache_path).decode(sys.getfilesystemencoding())
set_cache_path_base(self.album_path)
if not cache_hit: self.all_albums = list()
message("metainfo", os.path.basename(entry)) self.all_photos = list()
photo = Photo(entry, self.cache_path) self.walk(self.album_path)
if photo.is_valid: self.big_lists()
self.all_photos.append(photo) self.remove_stale()
album.add_photo(photo) message("complete", "")
else:
message("unreadable", os.path.basename(entry)) def walk(self, path):
back_level() next_level()
if not album.empty: if not os.access(path, os.R_OK | os.X_OK):
message("caching", os.path.basename(path)) message("access denied", os.path.basename(path))
album.cache(self.cache_path) back_level()
self.all_albums.append(album) return None
else: message("walking", os.path.basename(path))
message("empty", os.path.basename(path)) cache = os.path.join(self.cache_path, json_cache(path))
back_level() cached = False
return album cached_album = None
def big_lists(self): if os.path.exists(cache):
photo_list = [] try:
self.all_photos.sort() cached_album = Album.from_cache(cache)
for photo in self.all_photos: if file_mtime(path) <= file_mtime(cache):
photo_list.append(photo.path) message("full cache", os.path.basename(path))
message("caching", "all photos path list") cached = True
fp = open(os.path.join(self.cache_path, "all_photos.json"), 'w') album = cached_album
json.dump(photo_list, fp, cls=PhotoAlbumEncoder) for photo in album.photos:
fp.close() self.all_photos.append(photo)
def remove_stale(self): else:
message("cleanup", "building stale list") message("partial cache", os.path.basename(path))
all_cache_entries = { "all_photos.json": True, "latest_photos.json": True } except KeyboardInterrupt:
for album in self.all_albums: raise
all_cache_entries[album.cache_path] = True except:
for photo in self.all_photos: message("corrupt cache", os.path.basename(path))
for entry in photo.image_caches: cached_album = None
all_cache_entries[entry] = True if not cached:
message("cleanup", "searching for stale cache entries") album = Album(path)
for cache in os.listdir(self.cache_path): for entry in os.listdir(path):
try: if entry[0] == '.':
cache = cache.decode(sys.getfilesystemencoding()) continue
except KeyboardInterrupt: try:
raise entry = entry.decode(sys.getfilesystemencoding())
except: except KeyboardInterrupt:
pass raise
if cache not in all_cache_entries: except:
message("cleanup", os.path.basename(cache)) next_level()
os.unlink(os.path.join(self.cache_path, cache)) message("unicode error", entry.decode(
sys.getfilesystemencoding(), "replace"))
back_level()
continue
entry = os.path.join(path, entry)
if os.path.isdir(entry):
next_walked_album = self.walk(entry)
if next_walked_album is not None:
album.add_album(next_walked_album)
elif not cached and os.path.isfile(entry):
next_level()
cache_hit = False
if cached_album:
cached_photo = cached_album.photo_from_path(entry)
if (cached_photo and file_mtime(
entry) <= cached_photo.attributes["dateTimeFile"]):
cache_file = None
if "mediaType" in cached_photo.attributes:
if cached_photo.attributes["mediaType"] == "video":
# if video
cache_file = os.path.join(
self.cache_path, video_cache(entry))
else:
# if image
cache_file = os.path.join(
self.cache_path,
image_cache(entry, 1024, False))
else:
# if image
cache_file = os.path.join(
self.cache_path,
image_cache(entry, 1024, False))
# at this point we have full path to cache image/video
# check if it actually exists
if os.path.exists(cache_file):
message("cache hit", os.path.basename(entry))
cache_hit = True
photo = cached_photo
if not cache_hit:
message("metainfo", os.path.basename(entry))
photo = Photo(entry, self.cache_path)
if photo.is_valid:
self.all_photos.append(photo)
album.add_photo(photo)
else:
message("unreadable", os.path.basename(entry))
back_level()
if not album.empty:
message("caching", os.path.basename(path))
album.cache(self.cache_path)
self.all_albums.append(album)
else:
message("empty", os.path.basename(path))
back_level()
return album
def big_lists(self):
photo_list = []
self.all_photos.sort()
for photo in self.all_photos:
photo_list.append(photo.path)
message("caching", "all photos path list")
fp = open(os.path.join(self.cache_path, "all_photos.json"), 'w')
json.dump(photo_list, fp, cls=PhotoAlbumEncoder)
fp.close()
def remove_stale(self):
message("cleanup", "building stale list")
all_cache_entries = {"all_photos.json": True,
"latest_photos.json": True}
for album in self.all_albums:
all_cache_entries[album.cache_path] = True
for photo in self.all_photos:
for entry in photo.image_caches:
all_cache_entries[entry] = True
message("cleanup", "searching for stale cache entries")
for cache in os.listdir(self.cache_path):
try:
cache = cache.decode(sys.getfilesystemencoding())
except KeyboardInterrupt:
raise
except:
pass
if cache not in all_cache_entries:
message("cleanup", os.path.basename(cache))
os.unlink(os.path.join(self.cache_path, cache))

View File

@ -2,46 +2,49 @@ from CachePath import message
import os import os
import subprocess import subprocess
class VideoToolWrapper(object):
def call(self, *args):
path = args[-1]
for tool in self.wrappers:
try:
if self.check_output:
p = subprocess.check_output((tool,) + args)
else:
p = subprocess.call((tool,) + args)
if p > 0:
return False
else:
return "SUCCESS"
except KeyboardInterrupt:
if self.cleanup:
self.remove(path)
raise
except OSError:
continue
except:
if self.cleanup:
self.remove(path)
continue
return p
return False
def remove(self, path): class VideoToolWrapper(object):
try: def call(self, *args):
os.unlink(path) path = args[-1]
except: for tool in self.wrappers:
pass try:
if self.check_output:
p = subprocess.check_output((tool,) + args)
else:
p = subprocess.call((tool,) + args)
if p > 0:
return False
else:
return "SUCCESS"
except KeyboardInterrupt:
if self.cleanup:
self.remove(path)
raise
except OSError:
continue
except:
if self.cleanup:
self.remove(path)
continue
return p
return False
def remove(self, path):
try:
os.unlink(path)
except:
pass
class VideoTranscodeWrapper(VideoToolWrapper): class VideoTranscodeWrapper(VideoToolWrapper):
def __init__(self): def __init__(self):
self.wrappers = ['avconv', 'ffmpeg'] self.wrappers = ['avconv', 'ffmpeg']
self.check_output = False self.check_output = False
self.cleanup = True self.cleanup = True
class VideoProbeWrapper(VideoToolWrapper): class VideoProbeWrapper(VideoToolWrapper):
def __init__(self): def __init__(self):
self.wrappers = ['avprobe', 'ffprobe'] self.wrappers = ['avprobe', 'ffprobe']
self.check_output = True self.check_output = True
self.cleanup = False self.cleanup = False

View File

@ -3,7 +3,9 @@ from flask_login import LoginManager
import os.path import os.path
app = Flask(__name__) app = Flask(__name__)
app.config.from_pyfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "app.cfg")) app.config.from_pyfile(
os.path.join(os.path.dirname(os.path.abspath(__file__)), "app.cfg"))
login_manager = LoginManager() login_manager = LoginManager()
import login import login
login_manager.setup_app(app) login_manager.setup_app(app)

View File

@ -1,118 +1,160 @@
from floatapp import app
from floatapp.login import admin_required, login_required, is_authenticated, query_is_photo_user, query_is_admin_user, photo_user, admin_user
from floatapp.jsonp import jsonp
from process import send_process
from TreeWalker import TreeWalker
from flask import Response, abort, json, request, jsonify
from flask_login import login_user, current_user
from random import shuffle
import os import os
from mimetypes import guess_type from mimetypes import guess_type
from random import shuffle
from flask import Response, abort, json, jsonify, request
from flask_login import current_user, login_user
from floatapp import app
from floatapp.jsonp import jsonp
from floatapp.login import (admin_required, admin_user, is_authenticated,
login_required, photo_user, query_is_admin_user,
query_is_photo_user)
from process import send_process
from TreeWalker import TreeWalker
cwd = os.path.dirname(os.path.abspath(__file__)) cwd = os.path.dirname(os.path.abspath(__file__))
@app.route("/scan") @app.route("/scan")
@admin_required @admin_required
def scan_photos(): def scan_photos():
global cwd global cwd
response = send_process([ "stdbuf", "-oL", os.path.abspath(os.path.join(cwd, "../main.py")), response = send_process([
os.path.abspath(app.config["ALBUM_PATH"]), os.path.abspath(app.config["CACHE_PATH"]) ], "stdbuf",
os.path.join(cwd, "scanner.pid")) "-oL",
response.headers.add("X-Accel-Buffering", "no") os.path.abspath(os.path.join(cwd, "../venv/bin/python")),
response.cache_control.no_cache = True os.path.abspath(os.path.join(cwd, "../main.py")),
return response os.path.abspath(app.config["ALBUM_PATH"]),
os.path.abspath(app.config["CACHE_PATH"])
],
os.path.join(cwd, "scanner.pid"))
response.headers.add("X-Accel-Buffering", "no")
response.cache_control.no_cache = True
return response
@app.route("/auth") @app.route("/auth")
def login(): def login():
success = False success = False
if current_user.is_authenticated(): if current_user.is_authenticated:
success = True success = True
elif query_is_photo_user(request.form) or query_is_photo_user(request.args): elif (query_is_photo_user(request.form) or
success = login_user(photo_user, remember=True) query_is_photo_user(request.args)):
elif query_is_admin_user(request.form) or query_is_admin_user(request.args): success = login_user(photo_user, remember=True)
success = login_user(admin_user, remember=True) elif (query_is_admin_user(request.form) or
if not success: query_is_admin_user(request.args)):
abort(403) success = login_user(admin_user, remember=True)
return "" if not success:
abort(403)
return ""
def cache_base(path): def cache_base(path):
path = path.replace('/', '-').replace(' ', '_').replace('(', '').replace('&', '').replace(',', '').replace(')', '').replace('#', '').replace('[', '').replace(']', '').replace('"', '').replace("'", '').replace('_-_', '-').lower() path = path.replace(
while path.find("--") != -1: '/', '-').replace(
path = path.replace("--", "-") ' ', '_').replace(
while path.find("__") != -1: '(', '').replace(
path = path.replace("__", "_") '&', '').replace(
if len(path) == 0: ',', '').replace(
path = "root" ')', '').replace(
return path '#', '').replace(
'[', '').replace(
']', '').replace(
'"', '').replace(
"'", '').replace(
'_-_', '-').lower()
while path.find("--") != -1:
path = path.replace("--", "-")
while path.find("__") != -1:
path = path.replace("__", "_")
if len(path) == 0:
path = "root"
return path
auth_list = []
auth_list = [ ]
def read_auth_list(): def read_auth_list():
global auth_list, cwd global auth_list, cwd
f = open(os.path.join(cwd, "auth.txt"), "r") f = open(os.path.join(cwd, "auth.txt"), "r")
paths = [ ] paths = []
for path in f: for path in f:
path = path.strip() path = path.strip()
paths.append(path) paths.append(path)
paths.append(cache_base(path)) paths.append(cache_base(path))
f.close() f.close()
auth_list = paths auth_list = paths
# TODO: Make this run via inotify # TODO: Make this run via inotify
read_auth_list() read_auth_list()
def check_permissions(path): def check_permissions(path):
if not is_authenticated(): if not is_authenticated():
for auth_path in auth_list: for auth_path in auth_list:
if path.startswith(auth_path): if path.startswith(auth_path):
abort(403) abort(403)
@app.route("/albums/<path:path>") @app.route("/albums/<path:path>")
def albums(path): def albums(path):
check_permissions(path) check_permissions(path)
return accel_redirect(app.config["ALBUM_ACCEL"], app.config["ALBUM_PATH"], path) return accel_redirect(
app.config["ALBUM_ACCEL"], app.config["ALBUM_PATH"], path)
@app.route("/cache/<path:path>") @app.route("/cache/<path:path>")
def cache(path): def cache(path):
check_permissions(path) check_permissions(path)
return accel_redirect(app.config["CACHE_ACCEL"], app.config["CACHE_PATH"], path) return accel_redirect(
app.config["CACHE_ACCEL"], app.config["CACHE_PATH"], path)
def accel_redirect(internal, real, relative_name): def accel_redirect(internal, real, relative_name):
real_path = os.path.join(real, relative_name) real_path = os.path.join(real, relative_name)
internal_path = os.path.join(internal, relative_name) internal_path = os.path.join(internal, relative_name)
if not os.path.isfile(real_path): if not os.path.isfile(real_path):
abort(404) abort(404)
mimetype = None mimetype = None
types = guess_type(real_path) types = guess_type(real_path)
if len(types) != 0: if len(types) != 0:
mimetype = types[0] mimetype = types[0]
response = Response(mimetype=mimetype) response = Response(mimetype=mimetype)
response.headers.add("X-Accel-Redirect", internal_path) response.headers.add("X-Accel-Redirect", internal_path)
response.cache_control.public = True response.cache_control.public = True
if mimetype == "application/json": if mimetype == "application/json":
response.cache_control.max_age = 3600 response.cache_control.max_age = 3600
else: else:
response.cache_control.max_age = 29030400 response.cache_control.max_age = 29030400
return response return response
@app.route("/photos") @app.route("/photos")
@jsonp @jsonp
def photos(): def photos():
f = open(os.path.join(app.config["CACHE_PATH"], "all_photos.json"), "r") f = open(os.path.join(app.config["CACHE_PATH"], "all_photos.json"), "r")
photos = json.load(f) photos = json.load(f)
f.close() f.close()
if not is_authenticated(): if not is_authenticated():
def allowed(photo): def allowed(photo):
for auth_path in auth_list: for auth_path in auth_list:
if photo.startswith(auth_path): if photo.startswith(auth_path):
return False return False
return True return True
photos = [photo for photo in photos if allowed(photo)] photos = [photo for photo in photos if allowed(photo)]
count = int(request.args.get("count", len(photos))) count = int(request.args.get("count", len(photos)))
random = request.args.get("random") == "true" random = request.args.get("random") == "true"
if random: if random:
shuffle(photos) shuffle(photos)
else: else:
photos.reverse() photos.reverse()
response = jsonify(photos=photos[0:count]) response = jsonify(photos=photos[0:count])
response.cache_control.no_cache = True response.cache_control.no_cache = True
return response return response

View File

@ -5,14 +5,16 @@ import re
jsonp_validator = re.compile("^[a-zA-Z0-9_\-.]{1,128}$") jsonp_validator = re.compile("^[a-zA-Z0-9_\-.]{1,128}$")
def jsonp(f): def jsonp(f):
"""Wraps JSONified output for JSONP""" """Wraps JSONified output for JSONP"""
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
callback = request.args.get('callback', False) callback = request.args.get('callback', False)
if callback and jsonp_validator.match(callback): if callback and jsonp_validator.match(callback):
content = str(callback) + '(' + str(f(*args,**kwargs).data) + ')' content = str(callback) + '(' + str(f(*args, **kwargs).data) + ')'
return current_app.response_class(content, mimetype='application/javascript') return current_app.response_class(
else: content, mimetype='application/javascript')
return f(*args, **kwargs) else:
return decorated_function return f(*args, **kwargs)
return decorated_function

View File

@ -3,51 +3,66 @@ from flask import request, abort
from flask_login import current_user, UserMixin from flask_login import current_user, UserMixin
from functools import wraps from functools import wraps
class User(UserMixin): class User(UserMixin):
def __init__(self, id, admin=False): def __init__(self, id, admin=False):
self.admin = admin self.admin = admin
self.id = id self.id = id
photo_user = User("user") photo_user = User("user")
admin_user = User("admin", True) admin_user = User("admin", True)
@login_manager.user_loader @login_manager.user_loader
def load_user(id): def load_user(id):
if id == "user": if id == "user":
return photo_user return photo_user
elif id == "admin": elif id == "admin":
return admin_user return admin_user
return None return None
@login_manager.unauthorized_handler @login_manager.unauthorized_handler
def unauthorized(): def unauthorized():
return abort(403) return abort(403)
def login_required(fn): def login_required(fn):
@wraps(fn) @wraps(fn)
def decorated_view(*args, **kwargs): def decorated_view(*args, **kwargs):
if query_is_admin_user(request.args) or query_is_photo_user(request.args) or current_user.is_authenticated(): if (query_is_admin_user(request.args) or
return fn(*args, **kwargs) query_is_photo_user(request.args) or
return app.login_manager.unauthorized() current_user.is_authenticated):
return decorated_view return fn(*args, **kwargs)
return app.login_manager.unauthorized()
return decorated_view
def admin_required(fn): def admin_required(fn):
@wraps(fn) @wraps(fn)
def decorated_view(*args, **kwargs): def decorated_view(*args, **kwargs):
if query_is_admin_user(request.args) or (current_user.is_authenticated() and current_user.admin): if (query_is_admin_user(request.args) or
return fn(*args, **kwargs) (current_user.is_authenticated and current_user.admin)):
return app.login_manager.unauthorized() return fn(*args, **kwargs)
return decorated_view return app.login_manager.unauthorized()
return decorated_view
def query_is_photo_user(query): def query_is_photo_user(query):
username = query.get("username", None) username = query.get("username", None)
password = query.get("password", None) password = query.get("password", None)
return username == app.config["PHOTO_USERNAME"] and password == app.config["PHOTO_PASSWORD"] return username == (app.config["PHOTO_USERNAME"] and
password == app.config["PHOTO_PASSWORD"])
def query_is_admin_user(query): def query_is_admin_user(query):
username = query.get("username", None) username = query.get("username", None)
password = query.get("password", None) password = query.get("password", None)
return username == app.config["ADMIN_USERNAME"] and password == app.config["ADMIN_PASSWORD"] return username == (app.config["ADMIN_USERNAME"] and
password == app.config["ADMIN_PASSWORD"])
def is_authenticated(): def is_authenticated():
return query_is_admin_user(request.args) or query_is_photo_user(request.args) or current_user.is_authenticated() return (query_is_admin_user(request.args) or
query_is_photo_user(request.args) or
current_user.is_authenticated)

View File

@ -3,50 +3,61 @@ import subprocess
import os import os
import sys import sys
class ProcessWrapper(object): class ProcessWrapper(object):
def __init__(self, process, done): def __init__(self, process, done):
self.process = process self.process = process
self.done = done self.done = done
def close(self):
self.done() def close(self):
if self.process.returncode is not None: self.done()
return if self.process.returncode is not None:
self.process.stdout.close() return
self.process.terminate() self.process.stdout.close()
self.process.wait() self.process.terminate()
def __iter__(self): self.process.wait()
return self
def __del__(self): def __iter__(self):
self.close() return self
def next(self):
try: def __del__(self):
data = self.process.stdout.readline() self.close()
except:
self.close() def next(self):
raise StopIteration() try:
if data: data = self.process.stdout.readline()
return data except:
self.close() self.close()
raise StopIteration() raise StopIteration()
if data:
return data
self.close()
raise StopIteration()
def send_process(args, pid_file): def send_process(args, pid_file):
def setup_proc(): def setup_proc():
f = open(pid_file, "w") f = open(pid_file, "w")
f.write(str(os.getpid())) f.write(str(os.getpid()))
f.close() f.close()
os.close(0) os.close(0)
os.dup2(1, 2) os.dup2(1, 2)
def tear_down_proc():
try: def tear_down_proc():
os.unlink(pid_file) try:
except: os.unlink(pid_file)
pass except:
if os.path.exists(pid_file): pass
f = open(pid_file, "r")
pid = f.read() if os.path.exists(pid_file):
f.close() f = open(pid_file, "r")
if os.path.exists("/proc/%s/status" % pid): pid = f.read()
return Response("Scanner is already running.\n", mimetype="text/plain") f.close()
process = subprocess.Popen(args, close_fds=True, stdout=subprocess.PIPE, preexec_fn=setup_proc) if os.path.exists("/proc/%s/status" % pid):
response = ProcessWrapper(process, tear_down_proc) return Response(
return Response(response, direct_passthrough=True, mimetype="text/plain") "Scanner is already running.\n", mimetype="text/plain")
process = subprocess.Popen(
args, close_fds=True, stdout=subprocess.PIPE, preexec_fn=setup_proc)
response = ProcessWrapper(process, tear_down_proc)
return Response(response, direct_passthrough=True, mimetype="text/plain")

View File

@ -5,19 +5,21 @@ from CachePath import message
import sys import sys
import os import os
def main():
reload(sys)
sys.setdefaultencoding("UTF-8")
if len(sys.argv) != 3: def main():
print "usage: %s ALBUM_PATH CACHE_PATH" % sys.argv[0] reload(sys)
return sys.setdefaultencoding("UTF-8")
try:
os.umask(022) if len(sys.argv) != 3:
TreeWalker(sys.argv[1], sys.argv[2]) print "usage: %s ALBUM_PATH CACHE_PATH" % sys.argv[0]
except KeyboardInterrupt: return
message("keyboard", "CTRL+C pressed, quitting.")
sys.exit(-97) try:
os.umask(022)
TreeWalker(sys.argv[1], sys.argv[2])
except KeyboardInterrupt:
message("keyboard", "CTRL+C pressed, quitting.")
sys.exit(-97)
if __name__ == "__main__": if __name__ == "__main__":
main() main()