class DateQuery(PropertySet): year = Property(int) month = Property(int) reverse = Property(bool, default=False) @classmethod def FromRequest(self): q = DateQuery() if bottle.request.query.year not in (None, ''): q.year = bottle.request.query.year if bottle.request.query.month not in (None, ''): q.month = bottle.request.query.month if bottle.request.query.reverse not in (None, ''): q.reverse = (bottle.request.query.reverse == 'yes') return q def to_query_string(self): return urllib.parse.urlencode( ( ('year', str(self.year) or ''), ('month', str(self.month) or ''), ('reverse', 'yes' if self.reverse else 'no'), ) )
class JobQuery(PropertySet): prev_offset = Property(int) offset = Property(int, default=0) page_size = Property(int, default=25, required=True) state = Property(enum=State) @classmethod def FromRequest(self): eq = JobQuery() if bottle.request.query.prev_offset not in (None, ''): eq.prev_offset = bottle.request.query.prev_offset if bottle.request.query.offset not in (None, ''): eq.offset = bottle.request.query.offset if bottle.request.query.page_size not in (None, ''): eq.page_size = bottle.request.query.page_size if bottle.request.query.state not in (None, ''): eq.state = getattr(State, bottle.request.query.state) return eq def to_query_string(self): return urllib.parse.urlencode(( ('prev_offset', self.prev_offset or ''), ('offset', self.offset), ('page_size', self.page_size), ('state', self.state.value if self.state is not None else ''), ))
class Tag(PropertySet): tag = Property() count = Property(int, default=0) self_url = Property(calculated=True) def calculate_urls(self): self.self_url = '%s?%s' % ( App.BASE, TagQuery(tag=self.tag).to_query_string(), )
class JPEGExportOptions(PropertySet): entry_id = Property(int) entry_ids = Property(list) purpose = Property(enum=Purpose) version = Property(int) date = Property() longest_side = Property(int) folder = Property() filename = Property()
class DateStats(PropertySet): new = Property(int, none=0) pending = Property(int, none=0) keep = Property(int, none=0) purge = Property(int, none=0) todo = Property(int, none=0) wip = Property(int, none=0) final = Property(int, none=0) total = Property(int, none=0)
class JPEGImportOptions(PropertySet): entry_id = Property(int) source_path = Property() folder = Property() mime_type = Property() analyse = Property(bool) is_derivative = Property(bool, default=False) source_purpose = Property(enum=Purpose, default=Purpose.raw) source_version = Property(int)
class EntryQuery(Query): prev_offset = Property(int) offset = Property(int, default=0) page_size = Property(int, default=25, required=True) date = Property() state = Property(enum=State) delta = Property(int, default=0) reverse = Property(bool, default=False)
class JobStats(PropertySet): new = Property(int, none=0) acquired = Property(int, none=0) active = Property(int, none=0) done = Property(int, none=0) held = Property(int, none=0) failed = Property(int, none=0) total = Property(int, none=0)
class FlickrOptions(PropertySet): entry_id = Property(int) source_purpose = Property(enum=Purpose, default=Purpose.original) source_version = Property(int) title = Property() description = Property() tags = Property(list) is_public = Property(bool, default=True)
class EntryFeed(PropertySet): count = Property(int) total_count = Property(int) offset = Property(int) date = Property() state = Property(enum=State) entries = Property(Entry, is_list=True)
class Variant(PropertySet): store = Property() mime_type = Property() size = Property(int) purpose = Property(enum=Purpose, default=Purpose.original) source_purpose = Property(enum=Purpose) version = Property(int, default=0) source_version = Property(int, default=0) width = Property(int) height = Property(int) angle = Property(int) mirror = Property() description = Property() _patchable = 'description', 'angle', 'mirror', \ 'source_version', 'source_purpose', \ 'size', 'width', 'height' def __repr__(self): return '<Variant %s/%i (%s)>' % (self.purpose.value, self.version, self.mime_type) def get_extension(self): if self.mime_type == 'image/jpeg': return '.jpg' elif self.purpose is Purpose.raw: return '.' + self.mime_type.split('/')[1] else: extensions = mimetypes.guess_all_extensions(self.mime_type) if len(extensions) == 0: return '' else: return extensions[-1] def get_filename(self, id): extension = self.get_extension() hex_id = '%08x' % (id) version = '_%i' % self.version if self.version > 0 else '' filename = hex_id + version + extension return os.path.join(self.store, filename)
class RawImportOptions(PropertySet): entry_id = Property(int) source_path = Property() folder = Property() mime_type = Property()
class RawFetchOptions(PropertySet): entry_id = Property(int)
class AmendOptions(PropertySet): entry_id = Property(int) amend_metadata = Property(bool, default=True) amend_variants = Property(bool, default=True)
class TagQuery(Query): offset = Property(int, default=0) page_size = Property(int, default=25) tag = Property()
class Job(PropertySet): id = Property(int, name='_id') revision = Property(name='_rev') method = Property(required=True) state = Property(enum=State, default=State.new) priority = Property(int, default=1000) message = Property() release = Property(float) options = Property(wrap=True) created = Property(float) updated = Property(float) started = Property(float) stopped = Property(float) self_url = Property(calculated=True) _patchable = ('state', ) def calculate_urls(self): self.self_url = '%s/%i' % (App.BASE, self.id)
class StateQuery(Query): state = Property() soft = Property(bool, default=False)
class DummyOptions(PropertySet): time = Property(float, default=1.0)
class Publication(PropertySet): author = Property() publish_ts = Property() title = Property() comment = Property() pages = Property(type=Page, is_list=True)
class JobFeed(PropertySet): count = Property(int) total_count = Property(int) offset = Property(int) stats = Property(JobStats) entries = Property(Job, is_list=True)
class Backup(PropertySet): method = Property() key = Property() url = Property() source_purpose = Property(enum=Purpose, default=Purpose.original) source_version = Property(int, default=0)
class Date(PropertySet): id = Property(name='_id') revision = Property(name='_rev') date = Property(calculated=True) short = Property() full = Property() mimetype = Property(default='text/plain') count = Property(int, default=0, calculated=True) stats = Property(DateStats, calculated=True) entries = Property(dict, calculated=True) self_url = Property(calculated=True) date_url = Property(calculated=True) def calculate_urls(self): if self.date is None: self.date = self.id self.self_url = App.BASE + '/' + self.date self.date_url = '/entry?date=' + self.date if self.stats is not None: self.count = self.stats.total def decode(self): if self.short: self.short = decode_composed(self.short) if self.full: self.full = decode_composed(self.full)
class Entry(PropertySet): id = Property(int, name='_id') type = Property(enum=Type, default=Type.image) revision = Property(name='_rev') mime_type = Property() original_filename = Property() import_folder = Property() state = Property(enum=State) access = Property(enum=Access, default=Access.private) variants = Property(type=Variant, is_list=True) tags = Property(list) taken_ts = Property() metadata = Property(wrap=True) backups = Property(type=Backup, is_list=True) title = Property() description = Property() urls = Property(dict, calculated=True) state_url = Property(calculated=True) self_url = Property(calculated=True) original_url = Property(calculated=True) thumb_url = Property(calculated=True) proxy_url = Property(calculated=True) check_url = Property(calculated=True) raw_url = Property(calculated=True) derivative_url = Property(calculated=True) _patchable = 'title', 'description', 'tags' _decodeable = 'title', 'description', 'tags' def get_variant(self, purpose, version=None): variants = [ variant for variant in self.variants if variant.purpose == Purpose(purpose) ] if len(variants) == 0: return None if version is None: # take latest return sorted(variants, key=lambda variant: variant.version).pop() else: try: return [ variant for variant in variants if variant.version == version ][0] except IndexError: return None def get_filename(self, purpose, version=None): variant = self.get_variant(purpose, version=version) if variant is None: return None else: return variant.get_filename(self.id) def get_next_version(self, purpose): variants = [ variant for variant in self.variants if variant.purpose == Purpose(purpose) ] if len(variants) == 0: return 0 return max([ variant.version for variant in self.variants if variant.purpose is purpose ]) + 1 def has_backup(self, variant, method): return any( x for x in self.backups if x.source_purpose == variant.source_purpose and x.source_version == variant.source_version and x.method == method) def calculate_urls(self): self.self_url = '%s/%i' % (App.BASE, self.id) self.state_url = '%s/%i/state' % (App.BASE, self.id) self.urls = {} for variant in self.variants: if not variant.purpose.value in self.urls.keys(): self.urls[variant.purpose.value] = {} url = '%s/%i/dl/%s/%i%s' % (App.BASE, self.id, variant.store, variant.version, variant.get_extension()) self.urls[variant.purpose.value][variant.version] = url if variant.purpose is Purpose.original: self.original_url = url elif variant.purpose is Purpose.proxy: self.proxy_url = url elif variant.purpose is Purpose.thumb: self.thumb_url = url elif variant.purpose is Purpose.check: self.check_url = url elif variant.purpose is Purpose.raw: self.raw_url = url elif variant.purpose is Purpose.derivative: self.derivative_url = url def decode(self): for attr in self._decodeable: value = getattr(self, attr) if value: if attr == 'tags': setattr(self, attr, [decode_composed(x) for x in value]) else: setattr(self, attr, decode_composed(value))
class RemoteOptions(PropertySet): entry_id = Property(int) variant = Property(type=Variant) remote = Property()
class Page(PropertySet): number = Property(int) show = Property(bool, default=True) width = Property(int) height = Property(int) elements = Property(type=Element, is_list=True)
class DateFeed(PropertySet): count = Property(int) entries = Property(Date, is_list=True)
class TagFeed(PropertySet): page_size = Property(int) offset = Property(int) entries = Property(Tag, is_list=True)
class TagEntryFeed(PropertySet): tag = Property() page_size = Property(int) offset = Property(int) entries = Property(Entry, is_list=True)
class JPEGMetadata(PropertySet): Artist = Property() ColorSpace = Property() Copyright = Property() DateTime = Property() DateTimeDigitized = Property() DateTimeOriginal = Property() ExposureTime = Property(tuple) FNumber = Property(tuple) Flash = Property() FocalLength = Property(tuple) FocalLengthIn35mmFilm = Property(int) Geometry = Property(tuple) ISOSpeedRatings = Property(int) Make = Property() Model = Property() Orientation = Property() Mirror = Property() Angle = Property(int, default=0) Saturation = Property() Software = Property() SubjectDistanceRange = Property(int) WhiteBalance = Property() Latitude = Property() Longitude = Property()
class Element(PropertySet): type = Property(enum=ElementType, default=ElementType.image) src = Property() content = Property() entry_id = Property()