def compress_images(container, report=None, names=None, jpeg_quality=None, progress_callback=lambda n, t, name: True): images = get_compressible_images(container) if names is not None: images &= set(names) results = {} queue = Queue() abort = Event() for name in images: queue.put(name) def pc(name): keep_going = progress_callback(len(results), len(images), name) if not keep_going: abort.set() progress_callback(0, len(images), '') [ Worker(abort, 'CompressImage%d' % i, queue, results, container, jpeg_quality, pc) for i in range(min(detect_ncpus(), len(images))) ] queue.join() before_total = after_total = 0 changed = False for name, (ok, res) in results.iteritems(): name = force_unicode(name, filesystem_encoding) if ok: before, after = res if before != after: changed = True before_total += before after_total += after if report: if before != after: report( _('{0} compressed from {1} to {2} bytes [{3:.1%} reduction]' ).format(name, human_readable(before), human_readable(after), (before - after) / before)) else: report( _('{0} could not be further compressed').format(name)) else: report(_('Failed to process {0} with error:').format(name)) report(res) if report: if changed: report('') report( _('Total image filesize reduced from {0} to {1} [{2:.1%} reduction]' ).format(human_readable(before_total), human_readable(after_total), (before_total - after_total) / before_total)) else: report(_('Images are already fully optimized')) return changed, results
def paint(self, painter, option, index): top_level = not index.parent().isValid() hover = option.state & QStyle.State_MouseOver if hover: if top_level: suffix = '%s(%d)' % (NBSP, index.model().rowCount(index)) else: try: suffix = NBSP + human_readable( current_container().filesize( unicode(index.data(NAME_ROLE).toString()))) except EnvironmentError: suffix = NBSP + human_readable(0) br = painter.boundingRect(option.rect, Qt.AlignRight | Qt.AlignVCenter, suffix) if top_level and index.row() > 0: option.rect.adjust(0, 5, 0, 0) painter.drawLine(option.rect.topLeft(), option.rect.topRight()) option.rect.adjust(0, 1, 0, 0) if hover: option.rect.adjust(0, 0, -br.width(), 0) QStyledItemDelegate.paint(self, painter, option, index) if hover: option.rect.adjust(0, 0, br.width(), 0) painter.drawText(option.rect, Qt.AlignRight | Qt.AlignVCenter, suffix)
def paint(self, painter, option, index): top_level = not index.parent().isValid() hover = option.state & QStyle.StateFlag.State_MouseOver cc = current_container() def safe_size(index): try: return cc.filesize(str(index.data(NAME_ROLE) or '')) except OSError: return 0 if hover: if top_level: count = index.model().rowCount(index) total_size = human_readable(sum(safe_size(index.child(r, 0)) for r in range(count))) suffix = f'{NBSP}{count}@{total_size}' else: suffix = NBSP + human_readable(safe_size(index)) br = painter.boundingRect(option.rect, Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignVCenter, suffix) if top_level and index.row() > 0: option.rect.adjust(0, 5, 0, 0) painter.drawLine(option.rect.topLeft(), option.rect.topRight()) option.rect.adjust(0, 1, 0, 0) if hover: option.rect.adjust(0, 0, -br.width(), 0) QStyledItemDelegate.paint(self, painter, option, index) if hover: option.rect.adjust(0, 0, br.width(), 0) painter.drawText(option.rect, Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignVCenter, suffix)
def compress_images(container, report=None, names=None, jpeg_quality=None, progress_callback=lambda n, t, name:True): images = get_compressible_images(container) if names is not None: images &= set(names) results = {} queue = Queue() abort = Event() seen = set() num_to_process = 0 for name in sorted(images): path = os.path.abspath(container.get_file_path_for_processing(name)) path_key = os.path.normcase(path) if path_key not in seen: num_to_process += 1 queue.put((name, path, container.mime_map[name])) seen.add(path_key) def pc(name): keep_going = progress_callback(len(results), num_to_process, name) if not keep_going: abort.set() progress_callback(0, num_to_process, '') [Worker(abort, 'CompressImage%d' % i, queue, results, jpeg_quality, pc) for i in range(min(detect_ncpus(), num_to_process))] queue.join() before_total = after_total = 0 processed_num = 0 changed = False for name, (ok, res) in iteritems(results): name = force_unicode(name, filesystem_encoding) if ok: before, after = res if before != after: changed = True processed_num += 1 before_total += before after_total += after if report: if before != after: report(_('{0} compressed from {1} to {2} bytes [{3:.1%} reduction]').format( name, human_readable(before), human_readable(after), (before - after)/before)) else: report(_('{0} could not be further compressed').format(name)) else: report(_('Failed to process {0} with error:').format(name)) report(res) if report: if changed: report('') report(_('Total image filesize reduced from {0} to {1} [{2:.1%} reduction, {3} images changed]').format( human_readable(before_total), human_readable(after_total), (before_total - after_total)/before_total, processed_num)) else: report(_('Images are already fully optimized')) return changed, results
def add_diff(self, left_name, right_name, left_text, right_text, context=None, syntax=None, beautify=False): left_text, right_text = left_text or '', right_text or '' is_identical = len(left_text) == len( right_text) and left_text == right_text and left_name == right_name is_text = isinstance(left_text, type('')) and isinstance( right_text, type('')) left_name = left_name or '[%s]' % _('This file was added') right_name = right_name or '[%s]' % _('This file was removed') self.left.headers.append((self.left.blockCount() - 1, left_name)) self.right.headers.append((self.right.blockCount() - 1, right_name)) for v in (self.left, self.right): c = v.textCursor() c.movePosition(c.End) (c.insertBlock(), c.insertBlock(), c.insertBlock()) with BusyCursor(): if is_identical: for v in (self.left, self.right): c = v.textCursor() c.movePosition(c.End) c.insertText('[%s]\n\n' % _('The files are identical')) elif left_name != right_name and not left_text and not right_text: self.add_text_diff( _('[This file was renamed to %s]') % right_name, _('[This file was renamed from %s]') % left_name, context, None) for v in (self.left, self.right): v.appendPlainText('\n') elif is_text: self.add_text_diff(left_text, right_text, context, syntax, beautify=beautify) elif syntax == 'raster_image': self.add_image_diff(left_text, right_text) else: text = '[%s]' % _('Binary file of size: %s') left_text, right_text = text % human_readable( len(left_text)), text % human_readable(len(right_text)) self.add_text_diff(left_text, right_text, None, None) for v in (self.left, self.right): v.appendPlainText('\n')
def add_diff(self, left_name, right_name, left_text, right_text, context=None, syntax=None, beautify=False): left_text, right_text = left_text or '', right_text or '' is_identical = len(left_text) == len(right_text) and left_text == right_text and left_name == right_name is_text = isinstance(left_text, type('')) and isinstance(right_text, type('')) left_name = left_name or '[%s]'%_('This file was added') right_name = right_name or '[%s]'%_('This file was removed') self.left.headers.append((self.left.blockCount() - 1, left_name)) self.right.headers.append((self.right.blockCount() - 1, right_name)) for v in (self.left, self.right): c = v.textCursor() c.movePosition(c.End) (c.insertBlock(), c.insertBlock(), c.insertBlock()) with BusyCursor(): if is_identical: for v in (self.left, self.right): c = v.textCursor() c.movePosition(c.End) c.insertText('[%s]\n\n' % _('The files are identical')) elif left_name != right_name and not left_text and not right_text: self.add_text_diff(_('[This file was renamed to %s]') % right_name, _('[This file was renamed from %s]') % left_name, context, None) for v in (self.left, self.right): v.appendPlainText('\n') elif is_text: self.add_text_diff(left_text, right_text, context, syntax, beautify=beautify) elif syntax == 'raster_image': self.add_image_diff(left_text, right_text) else: text = '[%s]' % _('Binary file of size: %s') left_text, right_text = text % human_readable(len(left_text)), text % human_readable(len(right_text)) self.add_text_diff(left_text, right_text, None, None) for v in (self.left, self.right): v.appendPlainText('\n')
def paint(self, painter, option, index): name = index.data(Qt.ItemDataRole.DisplayRole) sz = human_readable(index.data(Qt.ItemDataRole.UserRole)) pmap = index.data(Qt.ItemDataRole.UserRole + 1) irect = option.rect.adjusted(0, 5, 0, -5) irect.setRight(irect.left() + 70) if pmap is None: pmap = QPixmap( current_container().get_file_path_for_processing(name)) scaled, nwidth, nheight = fit_image(pmap.width(), pmap.height(), irect.width(), irect.height()) if scaled: pmap = pmap.scaled( nwidth, nheight, transformMode=Qt.TransformationMode.SmoothTransformation) index.model().setData(index, pmap, Qt.ItemDataRole.UserRole + 1) x, y = (irect.width() - pmap.width()) // 2, (irect.height() - pmap.height()) // 2 r = irect.adjusted(x, y, -x, -y) QStyledItemDelegate.paint(self, painter, option, empty_index) painter.drawPixmap(r, pmap) trect = irect.adjusted(irect.width() + 10, 0, 0, 0) trect.setRight(option.rect.right()) painter.save() if option.state & QStyle.StateFlag.State_Selected: painter.setPen( QPen(option.palette.color(QPalette.ColorRole.HighlightedText))) painter.drawText( trect, Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft, name + '\n' + sz) painter.restore()
def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, QModelIndex()) theme = index.data(Qt.UserRole) if not theme: return painter.save() pixmap = index.data(Qt.DecorationRole) if pixmap and not pixmap.isNull(): rect = option.rect.adjusted(0, self.SPACING, COVER_SIZE[0] - option.rect.width(), - self.SPACING) painter.drawPixmap(rect, pixmap) if option.state & QStyle.State_Selected: painter.setPen(QPen(QApplication.instance().palette().highlightedText().color())) bottom = option.rect.bottom() - 2 painter.drawLine(0, bottom, option.rect.right(), bottom) if 'static-text' not in theme: theme['static-text'] = QStaticText(_( ''' <h1>{title}</h1> <p>by <i>{author}</i> with <b>{number}</b> icons [{size}]</p> <p>{description}</p> <p>Version: {version} Number of users: {usage}</p> '''.format(title=theme.get('title', _('Unknown')), author=theme.get('author', _('Unknown')), number=theme.get('number', 0), description=theme.get('description', ''), size=human_readable(theme.get('compressed-size', 0)), version=theme.get('version', 1), usage=theme.get('usage', 0), ))) painter.drawStaticText(COVER_SIZE[0] + self.SPACING, option.rect.top() + self.SPACING, theme['static-text']) painter.restore()
def add_image_diff(self, left_data, right_data): def load(data): p = QPixmap() p.loadFromData(bytes(data)) if data and p.isNull(): p = self.failed_img return p left_img, right_img = load(left_data), load(right_data) change = [] # Let any initial resizing of the window finish in case this is the # first diff, to avoid the expensive resize calculation later QApplication.processEvents(QEventLoop.ExcludeUserInputEvents | QEventLoop.ExcludeSocketNotifiers) for v, img, size in ((self.left, left_img, len(left_data)), (self.right, right_img, len(right_data))): c = v.textCursor() c.movePosition(c.End) start = c.block().blockNumber() lines, w = self.get_lines_for_image(img, v) c.movePosition(c.StartOfBlock) if size > 0: c.beginEditBlock() c.insertText(_('Size: {0} Resolution: {1}x{2}').format(human_readable(size), img.width(), img.height())) for i in xrange(lines + 1): c.insertBlock() change.extend((start, c.block().blockNumber())) c.insertBlock() c.endEditBlock() v.images[start] = (img, w, lines) change.append('replace' if left_data and right_data else 'delete' if left_data else 'insert') self.left.changes.append((change[0], change[1], change[-1])) self.right.changes.append((change[2], change[3], change[-1])) QApplication.processEvents(QEventLoop.ExcludeUserInputEvents | QEventLoop.ExcludeSocketNotifiers)
def current_image_changed(self): self.info_label.setText('') name = self.file_name if name is not None: data = self.container.raw_data(name, decode=False) self.cover_view.set_pixmap(data) self.info_label.setText('{0}x{1}px | {2}'.format( self.cover_view.pixmap.width(), self.cover_view.pixmap.height(), human_readable(len(data))))
def update_tooltips(self): for i, loc in enumerate(('main', 'carda', 'cardb')): t = self.tooltips[loc] if self.free[i] > -1: t += u'\n\n%s '%human_readable(self.free[i]) + _('available') ac = getattr(self, 'location_'+loc) ac.setToolTip(t) ac.setWhatsThis(t) ac.setStatusTip(t)
def subset_fonts(pdf_doc, log): all_fonts = pdf_doc.list_fonts(True) for font in all_fonts: if font['Subtype'] != 'Type0' and font['Data']: try: sfnt = Sfnt(font['Data']) except UnsupportedFont: continue if b'glyf' not in sfnt: continue num, gen = font['Reference'] glyphs = all_glyph_ids_in_w_arrays( (font['W'] or (), font['W2'] or ()), as_set=True) pdf_subset(sfnt, glyphs) data = sfnt()[0] log('Subset embedded font from: {} to {}'.format( human_readable(len(font['Data'])), human_readable(len(data)))) pdf_doc.replace_font_data(data, num, gen)
def update_tooltips(self): for i, loc in enumerate(('main', 'carda', 'cardb')): t = self.tooltips[loc] if self.free[i] > -1: t += u'\n\n%s ' % human_readable(self.free[i]) + _('available') ac = getattr(self, 'location_' + loc) ac.setToolTip(t) ac.setWhatsThis(t) ac.setStatusTip(t)
def compress_images(container, report=None, names=None, jpeg_quality=None, progress_callback=lambda n, t, name:True): images = get_compressible_images(container) if names is not None: images &= set(names) results = {} queue = Queue() abort = Event() for name in images: queue.put(name) def pc(name): keep_going = progress_callback(len(results), len(images), name) if not keep_going: abort.set() progress_callback(0, len(images), '') [Worker(abort, 'CompressImage%d' % i, queue, results, container, jpeg_quality, pc) for i in range(min(detect_ncpus(), len(images)))] queue.join() before_total = after_total = 0 changed = False for name, (ok, res) in iteritems(results): name = force_unicode(name, filesystem_encoding) if ok: before, after = res if before != after: changed = True before_total += before after_total += after if report: if before != after: report(_('{0} compressed from {1} to {2} bytes [{3:.1%} reduction]').format( name, human_readable(before), human_readable(after), (before - after)/before)) else: report(_('{0} could not be further compressed').format(name)) else: report(_('Failed to process {0} with error:').format(name)) report(res) if report: if changed: report('') report(_('Total image filesize reduced from {0} to {1} [{2:.1%} reduction]').format( human_readable(before_total), human_readable(after_total), (before_total - after_total)/before_total)) else: report(_('Images are already fully optimized')) return changed, results
def dump(self, prefix='', out=sys.stdout): c = '+' if self.is_folder else '-' data = ('%s children'%(sum(map(len, (self.files, self.folders)))) if self.is_folder else human_readable(self.size)) data += ' modified=%s'%self.last_mod_string line = '%s%s %s [id:%s %s]'%(prefix, c, self.name, self.object_id, data) prints(line, file=out) for c in (self.folders, self.files): for e in sorted(c, key=lambda x:sort_key(x.name)): e.dump(prefix=prefix+' ', out=out)
class TooLarge(BaseError): level = INFO MAX_SIZE = 260 * 1024 HELP = _( 'This HTML file is larger than %s. Too large HTML files can cause performance problems' ' on some e-book readers. Consider splitting this file into smaller sections.' ) % human_readable(MAX_SIZE) def __init__(self, name): BaseError.__init__(self, _('File too large'), name)
def paint(self, painter, option, index): top_level = not index.parent().isValid() hover = option.state & QStyle.State_MouseOver if hover: if top_level: suffix = '%s(%d)' % (NBSP, index.model().rowCount(index)) else: try: suffix = NBSP + human_readable(current_container().filesize(unicode(index.data(NAME_ROLE) or ''))) except EnvironmentError: suffix = NBSP + human_readable(0) br = painter.boundingRect(option.rect, Qt.AlignRight|Qt.AlignVCenter, suffix) if top_level and index.row() > 0: option.rect.adjust(0, 5, 0, 0) painter.drawLine(option.rect.topLeft(), option.rect.topRight()) option.rect.adjust(0, 1, 0, 0) if hover: option.rect.adjust(0, 0, -br.width(), 0) QStyledItemDelegate.paint(self, painter, option, index) if hover: option.rect.adjust(0, 0, br.width(), 0) painter.drawText(option.rect, Qt.AlignRight|Qt.AlignVCenter, suffix)
def render_field(self, db, book_id): try: field = db.pref('field_under_covers_in_grid', 'title') if field == 'size': ans = human_readable(db.field_for(field, book_id, default_value=0)) else: mi = db.get_proxy_metadata(book_id) display_name, ans, val, fm = mi.format_field_extended(field) if fm and fm['datatype'] == 'rating': ans = u'\u2605' * int(val/2.0) if val is not None else '' return '' if ans is None else unicode(ans) except Exception: if DEBUG: import traceback traceback.print_exc() return ''
def render_field(self, db, book_id): try: field = db.pref("field_under_covers_in_grid", "title") if field == "size": ans = human_readable(db.field_for(field, book_id, default_value=0)) else: mi = db.get_proxy_metadata(book_id) display_name, ans, val, fm = mi.format_field_extended(field) if fm and fm["datatype"] == "rating": ans = "\u2605" * int(val / 2.0) if val is not None else "" return "" if ans is None else unicode(ans) except Exception: if DEBUG: import traceback traceback.print_exc() return ""
def add_image_diff(self, left_data, right_data): def load(data): p = QPixmap() p.loadFromData(as_bytes(data)) try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() p.setDevicePixelRatio(dpr) if data and p.isNull(): p = self.failed_img return p left_img, right_img = load(left_data), load(right_data) change = [] # Let any initial resizing of the window finish in case this is the # first diff, to avoid the expensive resize calculation later QApplication.processEvents( QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents | QEventLoop.ProcessEventsFlag.ExcludeSocketNotifiers) for v, img, size in ((self.left, left_img, len(left_data)), (self.right, right_img, len(right_data))): c = v.textCursor() c.movePosition(c.End) start = c.block().blockNumber() lines, w = self.get_lines_for_image(img, v) c.movePosition(c.StartOfBlock) if size > 0: c.beginEditBlock() c.insertText( _('Size: {0} Resolution: {1}x{2}').format( human_readable(size), img.width(), img.height())) for i in range(lines + 1): c.insertBlock() change.extend((start, c.block().blockNumber())) c.insertBlock() c.endEditBlock() v.images[start] = (img, w, lines) change.append('replace' if left_data and right_data else 'delete' if left_data else 'insert') self.left.changes.append((change[0], change[1], change[-1])) self.right.changes.append((change[2], change[3], change[-1])) QApplication.processEvents( QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents | QEventLoop.ProcessEventsFlag.ExcludeSocketNotifiers)
def render_field(self, db, book_id): is_stars = False try: field = db.pref('field_under_covers_in_grid', 'title') if field == 'size': ans = human_readable(db.field_for(field, book_id, default_value=0)) else: mi = db.get_proxy_metadata(book_id) display_name, ans, val, fm = mi.format_field_extended(field) if fm and fm['datatype'] == 'rating': ans = rating_to_stars(val, fm['display'].get('allow_half_stars', False)) is_stars = True return ('' if ans is None else unicode_type(ans)), is_stars except Exception: if DEBUG: import traceback traceback.print_exc() return '', is_stars
def paint(self, painter, option, index): name = index.data(Qt.DisplayRole) sz = human_readable(index.data(Qt.UserRole)) pmap = index.data(Qt.UserRole+1) irect = option.rect.adjusted(0, 5, 0, -5) irect.setRight(irect.left() + 70) if pmap is None: pmap = QPixmap(current_container().get_file_path_for_processing(name)) scaled, nwidth, nheight = fit_image(pmap.width(), pmap.height(), irect.width(), irect.height()) if scaled: pmap = pmap.scaled(nwidth, nheight, transformMode=Qt.SmoothTransformation) index.model().setData(index, pmap, Qt.UserRole+1) x, y = (irect.width() - pmap.width())//2, (irect.height() - pmap.height())//2 r = irect.adjusted(x, y, -x, -y) QStyledItemDelegate.paint(self, painter, option, QModelIndex()) painter.drawPixmap(r, pmap) trect = irect.adjusted(irect.width() + 10, 0, 0, 0) trect.setRight(option.rect.right()) painter.save() if option.state & QStyle.State_Selected: painter.setPen(QPen(option.palette.color(option.palette.HighlightedText))) painter.drawText(trect, Qt.AlignVCenter | Qt.AlignLeft, name + '\n' + sz) painter.restore()
def human_readable_size(self): """ File size in human readable form """ return human_readable(self.size)
def export_lib_text(self, lpath, size=None): return _('{0} [Size: {1}]\nin {2}').format( os.path.basename(lpath), ('' if size < 0 else human_readable(size)) if size is not None else _('Calculating...'), os.path.dirname(lpath))
def mobile(self, start='1', num='25', sort='date', search='', _=None, order='descending'): ''' Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, 'start: %s is not an integer'%start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) if not search: search = '' if isbytestring(search): search = search.decode('UTF-8') ids = self.db.search_getting_ids(search.strip(), self.search_restriction) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: self.sort(items, sort, (order.lower().strip() == 'ascending')) CFM = self.db.field_metadata CKEYS = [key for key in sorted(custom_fields_to_display(self.db), key=lambda x:sort_key(CFM[x]['name']))] # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. books = [] for record in items[(start-1):(start-1)+num]: book = {'formats':record[FM['formats']], 'size':record[FM['size']]} if not book['formats']: book['formats'] = '' if not book['size']: book['size'] = 0 book['size'] = human_readable(book['size']) aus = record[FM['authors']] if record[FM['authors']] else __builtin__._('Unknown') aut_is = CFM['authors']['is_multiple'] authors = aut_is['list_to_ui'].join([i.replace('|', ',') for i in aus.split(',')]) book['authors'] = authors book['series_index'] = fmt_sidx(float(record[FM['series_index']])) book['series'] = record[FM['series']] book['tags'] = format_tag_string(record[FM['tags']], ',', no_tag_count=True) book['title'] = record[FM['title']] for x in ('timestamp', 'pubdate'): book[x] = strftime('%d %b, %Y', record[FM[x]]) book['id'] = record[FM['id']] books.append(book) for key in CKEYS: def concat(name, val): return '%s:#:%s'%(name, unicode(val)) mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]['datatype'] if datatype in ['comments']: continue if datatype == 'text' and CFM[key]['is_multiple']: book[key] = concat(name, format_tag_string(val, CFM[key]['is_multiple']['ui_to_list'], no_tag_count=True, joinval=CFM[key]['is_multiple']['list_to_ui'])) else: book[key] = concat(name, val) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) url_base = "/mobile?search=" + search+";order="+order+";sort="+sort+";num="+str(num) raw = html.tostring(build_index(books, num, search, sort, order, start, len(ids), url_base, CKEYS, self.opts.url_prefix), encoding='utf-8', pretty_print=True) # tostring's include_meta_content_type is broken raw = raw.replace('<head>', '<head>\n' '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">') return raw
def evaluate(self, formatter, kwargs, mi, locals, val): try: return human_readable(round(float(val))) except: return ""
def update_cg_cache_size(self, size): self.cover_grid_current_disk_cache.setText( _('Current space used: %s') % human_readable(size))
def mobile(self, start="1", num="25", sort="date", search="", _=None, order="descending"): """ Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching """ try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, "start: %s is not an integer" % start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, "num: %s is not an integer" % num) if not search: search = "" if isbytestring(search): search = search.decode("UTF-8") ids = self.search_for_books(search) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM["id"]] in ids] if sort is not None: self.sort(items, sort, (order.lower().strip() == "ascending")) CFM = self.db.field_metadata CKEYS = [key for key in sorted(custom_fields_to_display(self.db), key=lambda x: sort_key(CFM[x]["name"]))] # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. books = [] for record in items[(start - 1) : (start - 1) + num]: book = {"formats": record[FM["formats"]], "size": record[FM["size"]]} if not book["formats"]: book["formats"] = "" if not book["size"]: book["size"] = 0 book["size"] = human_readable(book["size"]) aus = record[FM["authors"]] if record[FM["authors"]] else __builtin__._("Unknown") aut_is = CFM["authors"]["is_multiple"] authors = aut_is["list_to_ui"].join([i.replace("|", ",") for i in aus.split(",")]) book["authors"] = authors book["series_index"] = fmt_sidx(float(record[FM["series_index"]])) book["series"] = record[FM["series"]] book["tags"] = format_tag_string(record[FM["tags"]], ",", no_tag_count=True) book["title"] = record[FM["title"]] for x in ("timestamp", "pubdate"): book[x] = strftime("%d %b, %Y", as_local_time(record[FM[x]])) book["id"] = record[FM["id"]] books.append(book) for key in CKEYS: def concat(name, val): return "%s:#:%s" % (name, unicode(val)) mi = self.db.get_metadata(record[CFM["id"]["rec_index"]], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]["datatype"] if datatype in ["comments"]: continue if datatype == "text" and CFM[key]["is_multiple"]: book[key] = concat( name, format_tag_string( val, CFM[key]["is_multiple"]["ui_to_list"], no_tag_count=True, joinval=CFM[key]["is_multiple"]["list_to_ui"], ), ) else: book[key] = concat(name, val) updated = self.db.last_modified() cherrypy.response.headers["Content-Type"] = "text/html; charset=utf-8" cherrypy.response.headers["Last-Modified"] = self.last_modified(updated) q = { b"search": search.encode("utf-8"), b"order": order.encode("utf-8"), b"sort": sort.encode("utf-8"), b"num": str(num).encode("utf-8"), } url_base = "/mobile?" + urlencode(q) ua = cherrypy.request.headers.get("User-Agent", "").strip() have_kobo_browser = self.is_kobo_browser(ua) raw = html.tostring( build_index( books, num, search, sort, order, start, len(ids), url_base, CKEYS, self.opts.url_prefix, have_kobo_browser=have_kobo_browser, ), encoding="utf-8", pretty_print=True, ) # tostring's include_meta_content_type is broken raw = raw.replace("<head>", "<head>\n" '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">') return raw
def mobile(self, start='1', num='25', sort='date', search='', _=None, order='descending'): ''' Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, 'start: %s is not an integer' % start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer' % num) if not search: search = '' if isbytestring(search): search = search.decode('UTF-8') ids = self.db.search_getting_ids(search.strip(), self.search_restriction) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: self.sort(items, sort, (order.lower().strip() == 'ascending')) CFM = self.db.field_metadata CKEYS = [ key for key in sorted(custom_fields_to_display(self.db), key=lambda x: sort_key(CFM[x]['name'])) ] # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. books = [] for record in items[(start - 1):(start - 1) + num]: book = { 'formats': record[FM['formats']], 'size': record[FM['size']] } if not book['formats']: book['formats'] = '' if not book['size']: book['size'] = 0 book['size'] = human_readable(book['size']) aus = record[FM['authors']] if record[ FM['authors']] else __builtin__._('Unknown') aut_is = CFM['authors']['is_multiple'] authors = aut_is['list_to_ui'].join( [i.replace('|', ',') for i in aus.split(',')]) book['authors'] = authors book['series_index'] = fmt_sidx(float(record[FM['series_index']])) book['series'] = record[FM['series']] book['tags'] = format_tag_string(record[FM['tags']], ',', no_tag_count=True) book['title'] = record[FM['title']] for x in ('timestamp', 'pubdate'): book[x] = strftime('%d %b, %Y', record[FM[x]]) book['id'] = record[FM['id']] books.append(book) for key in CKEYS: def concat(name, val): return '%s:#:%s' % (name, unicode(val)) mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]['datatype'] if datatype in ['comments']: continue if datatype == 'text' and CFM[key]['is_multiple']: book[key] = concat( name, format_tag_string( val, CFM[key]['is_multiple']['ui_to_list'], no_tag_count=True, joinval=CFM[key]['is_multiple']['list_to_ui'])) else: book[key] = concat(name, val) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' cherrypy.response.headers['Last-Modified'] = self.last_modified( updated) url_base = "/mobile?search=" + search + ";order=" + order + ";sort=" + sort + ";num=" + str( num) raw = html.tostring(build_index(books, num, search, sort, order, start, len(ids), url_base, CKEYS, self.opts.url_prefix), encoding='utf-8', pretty_print=True) # tostring's include_meta_content_type is broken raw = raw.replace( '<head>', '<head>\n' '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">' ) return raw
def main(): from calibre.utils.terminal import geometry cols = geometry()[0] parser = OptionParser( usage="usage: %prog [options] command args\n\ncommand " + "is one of: info, books, df, ls, cp, mkdir, touch, cat, rm, eject, test_file\n\n" + "For help on a particular command: %prog command", version=__appname__ + " version: " + __version__) parser.add_option( "--log-packets", help="print out packet stream to stdout. " + "The numbers in the left column are byte offsets that allow the packet size to be read off easily.", dest="log_packets", action="store_true", default=False) parser.remove_option("-h") parser.disable_interspersed_args() # Allow unrecognized options options, args = parser.parse_args() if len(args) < 1: parser.print_help() return 1 command = args[0] args = args[1:] dev = None scanner = DeviceScanner() scanner.scan() connected_devices = [] for d in device_plugins(): try: d.startup() except: print('Startup failed for device plugin: %s' % d) if d.MANAGES_DEVICE_PRESENCE: cd = d.detect_managed_devices(scanner.devices) if cd is not None: connected_devices.append((cd, d)) dev = d break continue ok, det = scanner.is_device_connected(d) if ok: dev = d dev.reset(log_packets=options.log_packets, detected_device=det) connected_devices.append((det, dev)) if dev is None: print('Unable to find a connected ebook reader.', file=sys.stderr) shutdown_plugins() return 1 for det, d in connected_devices: try: d.open(det, None) except: continue else: dev = d d.specialize_global_preferences(device_prefs) break try: if command == "df": total = dev.total_space(end_session=False) free = dev.free_space() where = ("Memory", "Card A", "Card B") print("Filesystem\tSize \tUsed \tAvail \tUse%") for i in range(3): print("%-10s\t%s\t%s\t%s\t%s" % (where[i], human_readable( total[i]), human_readable(total[i] - free[i]), human_readable(free[i]), unicode_type(0 if total[i] == 0 else int(100 * (total[i] - free[i]) / (total[i] * 1.))) + "%")) elif command == 'eject': dev.eject() elif command == "books": print("Books in main memory:") for book in dev.books(): print(book) print("\nBooks on storage carda:") for book in dev.books(oncard='carda'): print(book) print("\nBooks on storage cardb:") for book in dev.books(oncard='cardb'): print(book) elif command == "mkdir": parser = OptionParser( usage= "usage: %prog mkdir [options] path\nCreate a folder on the device\n\npath must begin with / or card:/" ) if len(args) != 1: parser.print_help() sys.exit(1) dev.mkdir(args[0]) elif command == "ls": parser = OptionParser( usage= "usage: %prog ls [options] path\nList files on the device\n\npath must begin with / or card:/" ) parser.add_option( "-l", help= "In addition to the name of each file, print the file type, permissions, and timestamp (the modification time, in the local timezone). Times are local.", # noqa dest="ll", action="store_true", default=False) parser.add_option( "-R", help= "Recursively list subfolders encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False) parser.remove_option("-h") parser.add_option("-h", "--human-readable", help="show sizes in human readable format", dest="hrs", action="store_true", default=False) options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 print(ls(dev, args[0], recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols), end=' ') elif command == "info": info(dev) elif command == "cp": usage="usage: %prog cp [options] source destination\nCopy files to/from the device\n\n"+\ "One of source or destination must be a path on the device. \n\nDevice paths have the form\n"+\ "dev:mountpoint/my/path\n"+\ "where mountpoint is one of / or carda: or cardb:/\n\n"+\ "source must point to a file for which you have read permissions\n"+\ "destination must point to a file or folder for which you have write permissions" parser = OptionParser(usage=usage) parser.add_option( '-f', '--force', dest='force', action='store_true', default=False, help='Overwrite the destination file if it exists already.') options, args = parser.parse_args(args) if len(args) != 2: parser.print_help() return 1 if args[0].startswith("dev:"): outfile = args[1] path = args[0][4:] if path.endswith("/"): path = path[:-1] if os.path.isdir(outfile): outfile = os.path.join(outfile, path[path.rfind("/") + 1:]) try: outfile = lopen(outfile, "wb") except IOError as e: print(e, file=sys.stderr) parser.print_help() return 1 dev.get_file(path, outfile) fsync(outfile) outfile.close() elif args[1].startswith("dev:"): try: infile = lopen(args[0], "rb") except IOError as e: print(e, file=sys.stderr) parser.print_help() return 1 dev.put_file(infile, args[1][4:], replace_file=options.force) infile.close() else: parser.print_help() return 1 elif command == "cat": outfile = sys.stdout parser = OptionParser( usage= "usage: %prog cat path\nShow file on the device\n\npath should point to a file on the device and must begin with /,a:/ or b:/" ) options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 if args[0].endswith("/"): path = args[0][:-1] else: path = args[0] outfile = sys.stdout dev.get_file(path, outfile) elif command == "rm": parser = OptionParser( usage= "usage: %prog rm path\nDelete files from the device\n\npath should point to a file or empty folder on the device " + "and must begin with / or card:/\n\n" + "rm will DELETE the file. Be very CAREFUL") options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 dev.rm(args[0]) elif command == "touch": parser = OptionParser( usage= "usage: %prog touch path\nCreate an empty file on the device\n\npath should point to a file on the device and must begin with /,a:/ or b:/\n\n" + # noqa "Unfortunately, I cant figure out how to update file times on the device, so if path already exists, touch does nothing" ) options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 dev.touch(args[0]) elif command == 'test_file': parser = OptionParser(usage=( "usage: %prog test_file path\n" 'Open device, copy file specified by path to device and ' 'then eject device.')) options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 path = args[0] from calibre.ebooks.metadata.meta import get_metadata mi = get_metadata(lopen(path, 'rb'), path.rpartition('.')[-1].lower()) print( dev.upload_books([args[0]], [os.path.basename(args[0])], end_session=False, metadata=[mi])) dev.eject() else: parser.print_help() if getattr(dev, 'handle', False): dev.close() return 1 except DeviceLocked: print("The device is locked. Use the --unlock option", file=sys.stderr) except (ArgumentError, DeviceError) as e: print(e, file=sys.stderr) return 1 finally: shutdown_plugins() return 0
def main(): from calibre.utils.terminal import geometry cols = geometry()[0] parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand "+ "is one of: info, books, df, ls, cp, mkdir, touch, cat, rm, eject, test_file\n\n"+ "For help on a particular command: %prog command", version=__appname__+" version: " + __version__) parser.add_option("--log-packets", help="print out packet stream to stdout. "+\ "The numbers in the left column are byte offsets that allow the packet size to be read off easily.", dest="log_packets", action="store_true", default=False) parser.remove_option("-h") parser.disable_interspersed_args() # Allow unrecognized options options, args = parser.parse_args() if len(args) < 1: parser.print_help() return 1 command = args[0] args = args[1:] dev = None scanner = DeviceScanner() scanner.scan() connected_devices = [] for d in device_plugins(): try: d.startup() except: print ('Startup failed for device plugin: %s'%d) if d.MANAGES_DEVICE_PRESENCE: cd = d.detect_managed_devices(scanner.devices) if cd is not None: connected_devices.append((cd, d)) dev = d break continue ok, det = scanner.is_device_connected(d) if ok: dev = d dev.reset(log_packets=options.log_packets, detected_device=det) connected_devices.append((det, dev)) if dev is None: print >>sys.stderr, 'Unable to find a connected ebook reader.' shutdown_plugins() return 1 for det, d in connected_devices: try: d.open(det, None) except: continue else: dev = d d.specialize_global_preferences(device_prefs) break try: if command == "df": total = dev.total_space(end_session=False) free = dev.free_space() where = ("Memory", "Card A", "Card B") print "Filesystem\tSize \tUsed \tAvail \tUse%" for i in range(3): print "%-10s\t%s\t%s\t%s\t%s"%(where[i], human_readable(total[i]), human_readable(total[i]-free[i]), human_readable(free[i]),\ str(0 if total[i]==0 else int(100*(total[i]-free[i])/(total[i]*1.)))+"%") elif command == 'eject': dev.eject() elif command == "books": print "Books in main memory:" for book in dev.books(): print book print "\nBooks on storage carda:" for book in dev.books(oncard='carda'): print book print "\nBooks on storage cardb:" for book in dev.books(oncard='cardb'): print book elif command == "mkdir": parser = OptionParser(usage="usage: %prog mkdir [options] path\nCreate a directory on the device\n\npath must begin with / or card:/") if len(args) != 1: parser.print_help() sys.exit(1) dev.mkdir(args[0]) elif command == "ls": parser = OptionParser(usage="usage: %prog ls [options] path\nList files on the device\n\npath must begin with / or card:/") parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time, in the local timezone). Times are local.", dest="ll", action="store_true", default=False) parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False) parser.remove_option("-h") parser.add_option("-h", "--human-readable", help="show sizes in human readable format", dest="hrs", action="store_true", default=False) options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 print ls(dev, args[0], recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols), elif command == "info": info(dev) elif command == "cp": usage="usage: %prog cp [options] source destination\nCopy files to/from the device\n\n"+\ "One of source or destination must be a path on the device. \n\nDevice paths have the form\n"+\ "dev:mountpoint/my/path\n"+\ "where mountpoint is one of / or card:/\n\n"+\ "source must point to a file for which you have read permissions\n"+\ "destination must point to a file or directory for which you have write permissions" parser = OptionParser(usage=usage) parser.add_option('-f', '--force', dest='force', action='store_true', default=False, help='Overwrite the destination file if it exists already.') options, args = parser.parse_args(args) if len(args) != 2: parser.print_help() return 1 if args[0].startswith("dev:"): outfile = args[1] path = args[0][7:] if path.endswith("/"): path = path[:-1] if os.path.isdir(outfile): outfile = os.path.join(outfile, path[path.rfind("/")+1:]) try: outfile = open(outfile, "wb") except IOError as e: print >> sys.stderr, e parser.print_help() return 1 dev.get_file(path, outfile) outfile.close() elif args[1].startswith("dev:"): try: infile = open(args[0], "rb") except IOError as e: print >> sys.stderr, e parser.print_help() return 1 try: dev.put_file(infile, args[1][7:]) except PathError as err: if options.force and 'exists' in str(err): dev.del_file(err.path, False) dev.put_file(infile, args[1][7:]) else: raise infile.close() else: parser.print_help() return 1 elif command == "cat": outfile = sys.stdout parser = OptionParser(usage="usage: %prog cat path\nShow file on the device\n\npath should point to a file on the device and must begin with /,a:/ or b:/") options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 if args[0].endswith("/"): path = args[0][:-1] else: path = args[0] outfile = sys.stdout dev.get_file(path, outfile) elif command == "rm": parser = OptionParser(usage="usage: %prog rm path\nDelete files from the device\n\npath should point to a file or empty directory on the device "+\ "and must begin with / or card:/\n\n"+\ "rm will DELETE the file. Be very CAREFUL") options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 dev.rm(args[0]) elif command == "touch": parser = OptionParser(usage="usage: %prog touch path\nCreate an empty file on the device\n\npath should point to a file on the device and must begin with /,a:/ or b:/\n\n"+ "Unfortunately, I cant figure out how to update file times on the device, so if path already exists, touch does nothing" ) options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 dev.touch(args[0]) elif command == 'test_file': parser = OptionParser(usage=("usage: %prog test_file path\n" 'Open device, copy file specified by path to device and ' 'then eject device.')) options, args = parser.parse_args(args) if len(args) != 1: parser.print_help() return 1 path = args[0] from calibre.ebooks.metadata.meta import get_metadata mi = get_metadata(open(path, 'rb'), path.rpartition('.')[-1].lower()) print dev.upload_books([args[0]], [os.path.basename(args[0])], end_session=False, metadata=[mi]) dev.eject() else: parser.print_help() if getattr(dev, 'handle', False): dev.close() return 1 except DeviceLocked: print >> sys.stderr, "The device is locked. Use the --unlock option" except (ArgumentError, DeviceError) as e: print >>sys.stderr, e return 1 finally: shutdown_plugins() return 0
def fget(self): return human_readable(self.size)
def evaluate(self, formatter, kwargs, mi, locals, val): try: return human_readable(round(float(val))) except: return ''