def opds_category(ctx, rd, category, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') if not which or not category: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) page_url = rc.url_for('/opds/category', which=which, category=category) up_url = rc.url_for('/opds/navcatalog', which=category) which, category = from_hex_unicode(which), from_hex_unicode(category) type_ = which[0] which = which[1:] if type_ == 'I': try: p = which.rindex(':') category = which[p + 1:] which = which[:p] # This line will toss an exception for composite columns which = int(which[:p]) except Exception: # Might be a composite column, where we have the lookup key if not (category in rc.db.field_metadata and rc.db.field_metadata[category]['datatype'] == 'composite'): raise HTTPNotFound('Tag %r not found' % which) categories = rc.get_categories() if category not in categories: raise HTTPNotFound('Category %r not found' % which) if category == 'search': try: ids = rc.search('search:"%s"' % which) except Exception: raise HTTPNotFound('Search: %r not understood' % which) return get_acquisition_feed(rc, ids, offset, page_url, up_url, 'calibre-search:' + which) if type_ != 'I': raise HTTPNotFound('Non id categories not supported') q = category if q == 'news': q = 'tags' ids = rc.db.get_books_for_category(q, which) & rc.allowed_book_ids() sort_by = 'series' if category == 'series' else 'title' return get_acquisition_feed(rc, ids, offset, page_url, up_url, 'calibre-category:' + category + ':' + unicode_type(which), sort_by=sort_by)
def opds_categorygroup(ctx, rd, category, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') if not which or not category: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) categories = rc.get_categories() page_url = rc.url_for('/opds/categorygroup', category=category, which=which) category = from_hex_unicode(category) if category not in categories: raise HTTPNotFound('Category %r not found' % which) category_meta = rc.db.field_metadata meta = category_meta.get(category, {}) category_name = meta.get('name', which) which = from_hex_unicode(which) feed_title = default_feed_title + ' :: ' + (_('By {0} :: {1}').format( category_name, which)) owhich = as_hex_unicode('N' + which) up_url = rc.url_for('/opds/navcatalog', which=owhich) items = categories[category] def belongs(x, which): return getattr(x, 'sort', x.name).lower().startswith(which.lower()) items = [x for x in items if belongs(x, which)] if not items: raise HTTPNotFound('No items in group %r:%r' % (category, which)) updated = rc.last_modified() id_ = 'calibre-category-group-feed:' + category + ':' + which max_items = rc.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset:offsets.offset + max_items] rc.outheaders['Last-Modified'] = http_date(timestampfromdt(updated)) return CategoryFeed(items, category, id_, updated, rc, offsets, page_url, up_url, title=feed_title).root
def opds_category(ctx, rd, category, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') if not which or not category: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) page_url = rc.url_for('/opds/category', which=which, category=category) up_url = rc.url_for('/opds/navcatalog', which=category) which, category = from_hex_unicode(which), from_hex_unicode(category) type_ = which[0] which = which[1:] if type_ == 'I': try: p = which.rindex(':') category = which[p+1:] which = which[:p] # This line will toss an exception for composite columns which = int(which[:p]) except Exception: # Might be a composite column, where we have the lookup key if not (category in rc.db.field_metadata and rc.db.field_metadata[category]['datatype'] == 'composite'): raise HTTPNotFound('Tag %r not found'%which) categories = rc.get_categories() if category not in categories: raise HTTPNotFound('Category %r not found'%which) if category == 'search': try: ids = rc.search('search:"%s"'%which) except Exception: raise HTTPNotFound('Search: %r not understood'%which) return get_acquisition_feed(rc, ids, offset, page_url, up_url, 'calibre-search:'+which) if type_ != 'I': raise HTTPNotFound('Non id categories not supported') q = category if q == 'news': q = 'tags' ids = rc.db.get_books_for_category(q, which) sort_by = 'series' if category == 'series' else 'title' return get_acquisition_feed(rc, ids, offset, page_url, up_url, 'calibre-category:'+category+':'+str(which), sort_by=sort_by)
def sendmail(self, attachment, aname, to, subject, text, log): logged = False while time.time() - self.last_send_time <= self.rate_limit: if not logged and self.rate_limit > 0: log('Waiting %s seconds before sending, to avoid being marked as spam.\nYou can control this delay via Preferences->Tweaks' % self.rate_limit) logged = True time.sleep(1) try: opts = email_config().parse() from_ = opts.from_ if not from_: from_ = 'calibre <calibre@'+socket.getfqdn()+'>' with lopen(attachment, 'rb') as f: msg = compose_mail(from_, to, text, subject, f, aname) efrom = extract_email_address(from_) eto = [] for x in to.split(','): eto.append(extract_email_address(x.strip())) def safe_debug(*args, **kwargs): try: return log.debug(*args, **kwargs) except Exception: pass sendmail(msg, efrom, eto, localhost=None, verbose=1, relay=opts.relay_host, username=opts.relay_username, password=from_hex_unicode(opts.relay_password), port=opts.relay_port, encryption=opts.encryption, debug_output=safe_debug) finally: self.last_send_time = time.time()
def sendmail(self, attachment, aname, to, subject, text, log): logged = False while time.time() - self.last_send_time <= self.rate_limit: if not logged and self.rate_limit > 0: log('Waiting %s seconds before sending, to avoid being marked as spam.\nYou can control this delay via Preferences->Tweaks' % self.rate_limit) logged = True time.sleep(1) try: opts = email_config().parse() from_ = opts.from_ if not from_: from_ = 'calibre <calibre@'+socket.getfqdn()+'>' with lopen(attachment, 'rb') as f: msg = compose_mail(from_, to, text, subject, f, aname) efrom = extract_email_address(from_) eto = [] for x in to.split(','): eto.append(extract_email_address(x.strip())) def safe_debug(*args, **kwargs): try: return log.debug(*args, **kwargs) except Exception: pass sendmail(msg, efrom, eto, localhost=None, verbose=1, relay=opts.relay_host, username=opts.relay_username, password=from_hex_unicode(opts.relay_password), port=opts.relay_port, encryption=opts.encryption, debug_output=safe_debug) finally: self.last_send_time = time.time()
def __init__(self, pa, parent): QDialog.__init__(self, parent) self.test_func = parent.test_email_settings self.setWindowTitle(_("Test email settings")) self.setWindowIcon(QIcon(I('config.ui'))) l = QVBoxLayout(self) opts = smtp_prefs().parse() self.from_ = la = QLabel(_("Send test mail from %s to:")%opts.from_) l.addWidget(la) self.to = le = QLineEdit(self) if pa: self.to.setText(pa) self.test_button = b = QPushButton(_('&Test'), self) b.clicked.connect(self.start_test) self.test_done.connect(self.on_test_done, type=Qt.QueuedConnection) self.h = h = QHBoxLayout() h.addWidget(le), h.addWidget(b) l.addLayout(h) if opts.relay_host: self.la = la = QLabel(_('Using: %(un)s:%(pw)s@%(host)s:%(port)s and %(enc)s encryption')% dict(un=opts.relay_username, pw=from_hex_unicode(opts.relay_password), host=opts.relay_host, port=opts.relay_port, enc=opts.encryption)) l.addWidget(la) self.log = QPlainTextEdit(self) l.addWidget(self.log) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close) bb.rejected.connect(self.reject), bb.accepted.connect(self.accept) l.addWidget(bb)
def initialize(self, preferred_to_address): self.preferred_to_address = preferred_to_address opts = smtp_prefs().parse() self.smtp_opts = opts if opts.from_: self.email_from.setText(opts.from_) self.email_from.textChanged.connect(self.changed) if opts.relay_host: self.relay_host.setText(opts.relay_host) self.relay_host.textChanged.connect(self.changed) self.relay_port.setValue(opts.relay_port) self.relay_port.valueChanged.connect(self.changed) if opts.relay_username: self.relay_username.setText(opts.relay_username) self.relay_username.textChanged.connect(self.changed) if opts.relay_password: self.relay_password.setText(from_hex_unicode(opts.relay_password)) self.relay_password.textChanged.connect(self.changed) getattr(self, 'relay_'+opts.encryption.lower()).setChecked(True) self.relay_tls.toggled.connect(self.changed) for x in ('gmx', 'hotmail'): button = getattr(self, 'relay_use_'+x) button.clicked.connect(partial(self.create_service_relay, x)) self.relay_show_password.stateChanged.connect( lambda state : self.relay_password.setEchoMode( self.relay_password.Password if state == 0 else self.relay_password.Normal)) self.test_email_button.clicked.connect(self.test_email)
def initialize(self, preferred_to_address): self.preferred_to_address = preferred_to_address opts = smtp_prefs().parse() self.smtp_opts = opts if opts.from_: self.email_from.setText(opts.from_) self.email_from.textChanged.connect(self.changed) if opts.relay_host: self.relay_host.setText(opts.relay_host) self.relay_host.textChanged.connect(self.changed) self.relay_port.setValue(opts.relay_port) self.relay_port.valueChanged.connect(self.changed) if opts.relay_username: self.relay_username.setText(opts.relay_username) self.relay_username.textChanged.connect(self.changed) if opts.relay_password: self.relay_password.setText(from_hex_unicode(opts.relay_password)) self.relay_password.textChanged.connect(self.changed) getattr(self, 'relay_'+opts.encryption.lower()).setChecked(True) self.relay_tls.toggled.connect(self.changed) for x in ('gmx', 'hotmail'): button = getattr(self, 'relay_use_'+x) button.clicked.connect(partial(self.create_service_relay, x)) self.relay_show_password.stateChanged.connect( lambda state : self.relay_password.setEchoMode( self.relay_password.Password if state == 0 else self.relay_password.Normal)) self.test_email_button.clicked.connect(self.test_email)
def __init__(self, pa, parent): QDialog.__init__(self, parent) self.test_func = parent.test_email_settings self.setWindowTitle(_("Test email settings")) self.setWindowIcon(QIcon(I('config.ui'))) l = QVBoxLayout(self) opts = smtp_prefs().parse() self.from_ = la = QLabel(_("Send test mail from %s to:")%opts.from_) l.addWidget(la) self.to = le = QLineEdit(self) if pa: self.to.setText(pa) self.test_button = b = QPushButton(_('&Test'), self) b.clicked.connect(self.start_test) self.test_done.connect(self.on_test_done, type=Qt.QueuedConnection) self.h = h = QHBoxLayout() h.addWidget(le), h.addWidget(b) l.addLayout(h) if opts.relay_host: self.la = la = QLabel(_('Using: %(un)s:%(pw)s@%(host)s:%(port)s and %(enc)s encryption')% dict(un=opts.relay_username, pw=from_hex_unicode(opts.relay_password), host=opts.relay_host, port=opts.relay_port, enc=opts.encryption)) l.addWidget(la) self.log = QPlainTextEdit(self) l.addWidget(self.log) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close) bb.rejected.connect(self.reject), bb.accepted.connect(self.accept) l.addWidget(bb)
def test_email_settings(self, to): opts = smtp_prefs().parse() from calibre.utils.smtp import sendmail, create_mail buf = PolyglotStringIO() debug_out = partial(prints, file=buf) oout, oerr = sys.stdout, sys.stderr sys.stdout = sys.stderr = buf tb = None try: msg = create_mail(opts.from_, to, 'Test mail from calibre', 'Test mail from calibre') sendmail(msg, from_=opts.from_, to=[to], verbose=3, timeout=30, relay=opts.relay_host, username=opts.relay_username, debug_output=debug_out, password=from_hex_unicode(opts.relay_password), encryption=opts.encryption, port=opts.relay_port) except: import traceback tb = traceback.format_exc() tb += '\n\nLog:\n' + buf.getvalue() finally: sys.stdout, sys.stderr = oout, oerr return tb
def opds_categorygroup(ctx, rd, category, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') if not which or not category: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) categories = rc.get_categories() page_url = rc.url_for('/opds/categorygroup', category=category, which=which) category = from_hex_unicode(category) if category not in categories: raise HTTPNotFound('Category %r not found'%which) category_meta = rc.db.field_metadata meta = category_meta.get(category, {}) category_name = meta.get('name', which) which = from_hex_unicode(which) feed_title = default_feed_title + ' :: ' + (_('By {0} :: {1}').format(category_name, which)) owhich = as_hex_unicode('N'+which) up_url = rc.url_for('/opds/navcatalog', which=owhich) items = categories[category] def belongs(x, which): return getattr(x, 'sort', x.name).lower().startswith(which.lower()) items = [x for x in items if belongs(x, which)] if not items: raise HTTPNotFound('No items in group %r:%r'%(category, which)) updated = rc.last_modified() id_ = 'calibre-category-group-feed:'+category+':'+which max_items = rc.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset:offsets.offset+max_items] rc.outheaders['Last-Modified'] = http_date(timestampfromdt(updated)) return CategoryFeed(items, category, id_, updated, rc, offsets, page_url, up_url, title=feed_title).root
def handle_click(self, link): typ, val = link.partition(':')[0::2] if typ == 'path': self.open_containing_folder.emit(int(val)) elif typ == 'format': id_, fmt = val.split(':') self.view_specific_format.emit(int(id_), fmt) elif typ == 'devpath': self.view_device_book.emit(val) elif typ == 'search': self.search_requested.emit(from_hex_unicode(val)) else: try: safe_open_url(QUrl(link, QUrl.TolerantMode)) except: import traceback traceback.print_exc()
def opds_navcatalog(ctx, rd, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) page_url = rc.url_for('/opds/navcatalog', which=which) up_url = rc.url_for('/opds') which = from_hex_unicode(which) type_ = which[0] which = which[1:] if type_ == 'O': return get_all_books(rc, which, page_url, up_url, offset=offset) elif type_ == 'N': return get_navcatalog(rc, which, page_url, up_url, offset=offset) raise HTTPNotFound('Not found')
def handle_click(self, link): typ, val = link.partition(':')[0::2] if typ == 'path': self.open_containing_folder.emit(int(val)) elif typ == 'format': id_, fmt = val.split(':') self.view_specific_format.emit(int(id_), fmt) elif typ == 'devpath': self.view_device_book.emit(val) elif typ == 'search': self.search_requested.emit(from_hex_unicode(val)) else: try: open_url(QUrl(link, QUrl.TolerantMode)) except: import traceback traceback.print_exc()
def opds_navcatalog(ctx, rd, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) page_url = rc.url_for('/opds/navcatalog', which=which) up_url = rc.url_for('/opds') which = from_hex_unicode(which) type_ = which[0] which = which[1:] if type_ == 'O': return get_all_books(rc, which, page_url, up_url, offset=offset) elif type_ == 'N': return get_navcatalog(rc, which, page_url, up_url, offset=offset) raise HTTPNotFound('Not found')
def test_email_settings(self, to): opts = smtp_prefs().parse() from calibre.utils.smtp import sendmail, create_mail buf = PolyglotBytesIO() debug_out = partial(prints, file=buf) oout, oerr = sys.stdout, sys.stderr sys.stdout = sys.stderr = buf tb = None try: msg = create_mail(opts.from_, to, 'Test mail from calibre', 'Test mail from calibre') sendmail(msg, from_=opts.from_, to=[to], verbose=3, timeout=30, relay=opts.relay_host, username=opts.relay_username, debug_output=debug_out, password=from_hex_unicode(opts.relay_password), encryption=opts.encryption, port=opts.relay_port) except: import traceback tb = traceback.format_exc() tb += '\n\nLog:\n' + buf.getvalue().decode('utf-8', 'replace') finally: sys.stdout, sys.stderr = oout, oerr return tb
def main(): if iswindows: if '--multiprocessing-fork' in sys.argv: # We are using the multiprocessing module on windows to launch a # worker process from multiprocessing import freeze_support freeze_support() return 0 if ismacos and 'CALIBRE_WORKER_FD' not in os.environ and 'CALIBRE_SIMPLE_WORKER' not in os.environ and '--pipe-worker' not in sys.argv: # On some OS X computers launchd apparently tries to # launch the last run process from the bundle # so launch the gui as usual from calibre.gui2.main import main as gui_main return gui_main(['calibre']) niceness = os.environ.pop('CALIBRE_WORKER_NICENESS', None) if niceness: try: os.nice(int(niceness)) except Exception: pass csw = os.environ.pop('CALIBRE_SIMPLE_WORKER', None) if csw: mod, _, func = csw.partition(':') mod = importlib.import_module(mod) func = getattr(mod, func) func() return if '--pipe-worker' in sys.argv: try: exec(sys.argv[-1]) except Exception: print('Failed to run pipe worker with command:', sys.argv[-1]) sys.stdout.flush() raise return fd = int(os.environ['CALIBRE_WORKER_FD']) resultf = from_hex_unicode(os.environ['CALIBRE_WORKER_RESULT']) with Connection(fd) as conn: name, args, kwargs, desc = eintr_retry_call(conn.recv) if desc: prints(desc) sys.stdout.flush() func, notification = get_func(name) notifier = Progress(conn) if notification: kwargs[notification] = notifier notifier.start() result = func(*args, **kwargs) if result is not None: os.makedirs(os.path.dirname(resultf), exist_ok=True) with lopen(resultf, 'wb') as f: f.write(pickle_dumps(result)) notifier.queue.put(None) try: sys.stdout.flush() except OSError: pass # Happens sometimes on OS X for GUI processes (EPIPE) try: sys.stderr.flush() except OSError: pass # Happens sometimes on OS X for GUI processes (EPIPE) return 0
def test_get(self): # {{{ 'Test /get' with self.create_server() as server: db = server.handler.router.ctx.library_broker.get(None) conn = server.connect() def get(what, book_id, library_id=None, q=''): q = ('?' + q) if q else q conn.request( 'GET', '/get/%s/%s' % (what, book_id) + (('/' + library_id) if library_id else '') + q) r = conn.getresponse() return r, r.read() # Test various invalid parameters def bad(*args): r, data = get(*args) self.ae(r.status, http_client.NOT_FOUND) bad('xxx', 1) bad('fmt1', 10) bad('fmt1', 1, 'zzzz') bad('fmt1', 'xx') # Test simple fetching of format without metadata update r, data = get('fmt1', 1, db.server_library_id) self.ae(data, db.format(1, 'fmt1')) self.assertIsNotNone(r.getheader('Content-Disposition')) self.ae(r.getheader('Used-Cache'), 'no') r, data = get('fmt1', 1) self.ae(data, db.format(1, 'fmt1')) self.ae(r.getheader('Used-Cache'), 'yes') # Test fetching of format with metadata update raw = P('quick_start/eng.epub', data=True) r, data = get('epub', 1) self.ae(r.status, http_client.OK) etag = r.getheader('ETag') self.assertIsNotNone(etag) self.ae(r.getheader('Used-Cache'), 'no') self.assertTrue(data.startswith(b'PK')) self.assertGreaterEqual(len(data), len(raw)) db.set_field('title', {1: 'changed'}) r, data = get('epub', 1) self.assertNotEqual(r.getheader('ETag'), etag) etag = r.getheader('ETag') self.ae(r.getheader('Used-Cache'), 'no') mi = get_metadata(BytesIO(data), extract_cover=False) self.ae(mi.title, 'changed') r, data = get('epub', 1) self.ae(r.getheader('Used-Cache'), 'yes') # Test plugboards import calibre.library.save_to_disk as c orig, c.DEBUG = c.DEBUG, False try: db.set_pref( 'plugboards', { u'epub': { u'content_server': [[u'changed, {title}', u'title']] } }) # this is needed as the cache is not invalidated for plugboard changes db.set_field('title', {1: 'again'}) r, data = get('epub', 1) self.assertNotEqual(r.getheader('ETag'), etag) etag = r.getheader('ETag') self.ae(r.getheader('Used-Cache'), 'no') mi = get_metadata(BytesIO(data), extract_cover=False) self.ae(mi.title, 'changed, again') finally: c.DEBUG = orig # Test the serving of covers def change_cover(count, book_id=2): cpath = db.format_abspath(book_id, '__COVER_INTERNAL__') db.set_cover({2: I('lt.png', data=True)}) t = time.time() + 1 + count # Ensure mtime changes, needed on OS X where HFS+ has a 1s # mtime resolution os.utime(cpath, (t, t)) r, data = get('cover', 1) self.ae(r.status, http_client.OK) self.ae(data, db.cover(1)) self.ae(r.getheader('Used-Cache'), 'no') self.ae(r.getheader('Content-Type'), 'image/jpeg') r, data = get('cover', 1) self.ae(r.status, http_client.OK) self.ae(data, db.cover(1)) self.ae(r.getheader('Used-Cache'), 'yes') r, data = get('cover', 3) self.ae(r.status, http_client.OK) # Auto generated cover r, data = get('thumb', 1) self.ae(r.status, http_client.OK) self.ae(identify(data), ('jpeg', 60, 60)) self.ae(r.getheader('Used-Cache'), 'no') r, data = get('thumb', 1) self.ae(r.status, http_client.OK) self.ae(r.getheader('Used-Cache'), 'yes') r, data = get('thumb', 1, q='sz=100') self.ae(r.status, http_client.OK) self.ae(identify(data), ('jpeg', 100, 100)) self.ae(r.getheader('Used-Cache'), 'no') r, data = get('thumb', 1, q='sz=100x100') self.ae(r.status, http_client.OK) self.ae(r.getheader('Used-Cache'), 'yes') change_cover(1, 1) r, data = get('thumb', 1, q='sz=100') self.ae(r.status, http_client.OK) self.ae(identify(data), ('jpeg', 100, 100)) self.ae(r.getheader('Used-Cache'), 'no') # Test file sharing in cache r, data = get('cover', 2) self.ae(r.status, http_client.OK) self.ae(data, db.cover(2)) self.ae(r.getheader('Used-Cache'), 'no') path = from_hex_unicode(r.getheader('Tempfile')) f, fdata = share_open(path, 'rb'), data # Now force an update change_cover(1) r, data = get('cover', 2) self.ae(r.status, http_client.OK) self.ae(data, db.cover(2)) self.ae(r.getheader('Used-Cache'), 'no') path = from_hex_unicode(r.getheader('Tempfile')) f2, f2data = share_open(path, 'rb'), data # Do it again change_cover(2) r, data = get('cover', 2) self.ae(r.status, http_client.OK) self.ae(data, db.cover(2)) self.ae(r.getheader('Used-Cache'), 'no') self.ae(f.read(), fdata) self.ae(f2.read(), f2data) # Test serving of metadata as opf r, data = get('opf', 1) self.ae(r.status, http_client.OK) self.ae(r.getheader('Content-Type'), 'application/oebps-package+xml; charset=UTF-8') self.assertIsNotNone(r.getheader('Last-Modified')) opf = OPF(BytesIO(data), populate_spine=False, try_to_guess_cover=False) self.ae(db.field_for('title', 1), opf.title) self.ae(db.field_for('authors', 1), tuple(opf.authors)) conn.request('GET', '/get/opf/1', headers={'Accept-Encoding': 'gzip'}) r = conn.getresponse() self.ae(r.status, http_client.OK), self.ae(r.getheader('Content-Encoding'), 'gzip') raw = r.read() self.ae(zlib.decompress(raw, 16 + zlib.MAX_WBITS), data) # Test serving metadata as json r, data = get('json', 1) self.ae(r.status, http_client.OK) self.ae(db.field_for('title', 1), json.loads(data)['title']) conn.request('GET', '/get/json/1', headers={'Accept-Encoding': 'gzip'}) r = conn.getresponse() self.ae(r.status, http_client.OK), self.ae(r.getheader('Content-Encoding'), 'gzip') raw = r.read() self.ae(zlib.decompress(raw, 16 + zlib.MAX_WBITS), data)
def test_get(self): # {{{ 'Test /get' with self.create_server() as server: db = server.handler.router.ctx.library_broker.get(None) conn = server.connect() def get(what, book_id, library_id=None, q=''): q = ('?' + q) if q else q conn.request('GET', '/get/%s/%s' % (what, book_id) + (('/' + library_id) if library_id else '') + q) r = conn.getresponse() return r, r.read() # Test various invalid parameters def bad(*args): r, data = get(*args) self.ae(r.status, http_client.NOT_FOUND) bad('xxx', 1) bad('fmt1', 10) bad('fmt1', 1, 'zzzz') bad('fmt1', 'xx') # Test simple fetching of format without metadata update r, data = get('fmt1', 1, db.server_library_id) self.ae(data, db.format(1, 'fmt1')) self.assertIsNotNone(r.getheader('Content-Disposition')) self.ae(r.getheader('Used-Cache'), 'no') r, data = get('fmt1', 1) self.ae(data, db.format(1, 'fmt1')) self.ae(r.getheader('Used-Cache'), 'yes') # Test fetching of format with metadata update raw = P('quick_start/eng.epub', data=True) r, data = get('epub', 1) self.ae(r.status, http_client.OK) etag = r.getheader('ETag') self.assertIsNotNone(etag) self.ae(r.getheader('Used-Cache'), 'no') self.assertTrue(data.startswith(b'PK')) self.assertGreaterEqual(len(data), len(raw)) db.set_field('title', {1:'changed'}) r, data = get('epub', 1) self.assertNotEqual(r.getheader('ETag'), etag) etag = r.getheader('ETag') self.ae(r.getheader('Used-Cache'), 'no') mi = get_metadata(BytesIO(data), extract_cover=False) self.ae(mi.title, 'changed') r, data = get('epub', 1) self.ae(r.getheader('Used-Cache'), 'yes') # Test plugboards import calibre.library.save_to_disk as c orig, c.DEBUG = c.DEBUG, False try: db.set_pref('plugboards', {u'epub': {u'content_server': [[u'changed, {title}', u'title']]}}) # this is needed as the cache is not invalidated for plugboard changes db.set_field('title', {1:'again'}) r, data = get('epub', 1) self.assertNotEqual(r.getheader('ETag'), etag) etag = r.getheader('ETag') self.ae(r.getheader('Used-Cache'), 'no') mi = get_metadata(BytesIO(data), extract_cover=False) self.ae(mi.title, 'changed, again') finally: c.DEBUG = orig # Test the serving of covers def change_cover(count, book_id=2): cpath = db.format_abspath(book_id, '__COVER_INTERNAL__') db.set_cover({2:I('lt.png', data=True)}) t = time.time() + 1 + count # Ensure mtime changes, needed on OS X where HFS+ has a 1s # mtime resolution os.utime(cpath, (t, t)) r, data = get('cover', 1) self.ae(r.status, http_client.OK) self.ae(data, db.cover(1)) self.ae(r.getheader('Used-Cache'), 'no') self.ae(r.getheader('Content-Type'), 'image/jpeg') r, data = get('cover', 1) self.ae(r.status, http_client.OK) self.ae(data, db.cover(1)) self.ae(r.getheader('Used-Cache'), 'yes') r, data = get('cover', 3) self.ae(r.status, http_client.OK) # Auto generated cover r, data = get('thumb', 1) self.ae(r.status, http_client.OK) self.ae(identify(data), ('jpeg', 60, 60)) self.ae(r.getheader('Used-Cache'), 'no') r, data = get('thumb', 1) self.ae(r.status, http_client.OK) self.ae(r.getheader('Used-Cache'), 'yes') r, data = get('thumb', 1, q='sz=100') self.ae(r.status, http_client.OK) self.ae(identify(data), ('jpeg', 100, 100)) self.ae(r.getheader('Used-Cache'), 'no') r, data = get('thumb', 1, q='sz=100x100') self.ae(r.status, http_client.OK) self.ae(r.getheader('Used-Cache'), 'yes') change_cover(1, 1) r, data = get('thumb', 1, q='sz=100') self.ae(r.status, http_client.OK) self.ae(identify(data), ('jpeg', 100, 100)) self.ae(r.getheader('Used-Cache'), 'no') # Test file sharing in cache r, data = get('cover', 2) self.ae(r.status, http_client.OK) self.ae(data, db.cover(2)) self.ae(r.getheader('Used-Cache'), 'no') path = from_hex_unicode(r.getheader('Tempfile')) f, fdata = share_open(path, 'rb'), data # Now force an update change_cover(1) r, data = get('cover', 2) self.ae(r.status, http_client.OK) self.ae(data, db.cover(2)) self.ae(r.getheader('Used-Cache'), 'no') path = from_hex_unicode(r.getheader('Tempfile')) f2, f2data = share_open(path, 'rb'), data # Do it again change_cover(2) r, data = get('cover', 2) self.ae(r.status, http_client.OK) self.ae(data, db.cover(2)) self.ae(r.getheader('Used-Cache'), 'no') self.ae(f.read(), fdata) self.ae(f2.read(), f2data) # Test serving of metadata as opf r, data = get('opf', 1) self.ae(r.status, http_client.OK) self.ae(r.getheader('Content-Type'), 'application/oebps-package+xml; charset=UTF-8') self.assertIsNotNone(r.getheader('Last-Modified')) opf = OPF(BytesIO(data), populate_spine=False, try_to_guess_cover=False) self.ae(db.field_for('title', 1), opf.title) self.ae(db.field_for('authors', 1), tuple(opf.authors)) conn.request('GET', '/get/opf/1', headers={'Accept-Encoding':'gzip'}) r = conn.getresponse() self.ae(r.status, http_client.OK), self.ae(r.getheader('Content-Encoding'), 'gzip') raw = r.read() self.ae(zlib.decompress(raw, 16+zlib.MAX_WBITS), data) # Test serving metadata as json r, data = get('json', 1) self.ae(r.status, http_client.OK) self.ae(db.field_for('title', 1), json.loads(data)['title']) conn.request('GET', '/get/json/1', headers={'Accept-Encoding':'gzip'}) r = conn.getresponse() self.ae(r.status, http_client.OK), self.ae(r.getheader('Content-Encoding'), 'gzip') raw = r.read() self.ae(zlib.decompress(raw, 16+zlib.MAX_WBITS), data)
def main(): if iswindows: if '--multiprocessing-fork' in sys.argv: # We are using the multiprocessing module on windows to launch a # worker process from multiprocessing import freeze_support freeze_support() return 0 # Close open file descriptors inherited from parent # On Unix this is done by the subprocess module os.closerange(3, 256) if isosx and 'CALIBRE_WORKER_ADDRESS' not in os.environ and 'CALIBRE_SIMPLE_WORKER' not in os.environ and '--pipe-worker' not in sys.argv: # On some OS X computers launchd apparently tries to # launch the last run process from the bundle # so launch the gui as usual from calibre.gui2.main import main as gui_main return gui_main(['calibre']) csw = os.environ.get('CALIBRE_SIMPLE_WORKER', None) if csw: mod, _, func = csw.partition(':') mod = importlib.import_module(mod) func = getattr(mod, func) func() return if '--pipe-worker' in sys.argv: try: exec (sys.argv[-1]) except Exception: print('Failed to run pipe worker with command:', sys.argv[-1]) raise return address = msgpack_loads(from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) resultf = from_hex_unicode(os.environ['CALIBRE_WORKER_RESULT']) with closing(Client(address, authkey=key)) as conn: name, args, kwargs, desc = eintr_retry_call(conn.recv) if desc: prints(desc) sys.stdout.flush() func, notification = get_func(name) notifier = Progress(conn) if notification: kwargs[notification] = notifier notifier.start() result = func(*args, **kwargs) if result is not None and os.path.exists(os.path.dirname(resultf)): with lopen(resultf, 'wb') as f: f.write(pickle_dumps(result)) notifier.queue.put(None) try: sys.stdout.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) try: sys.stderr.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) return 0
def main(): if iswindows: if '--multiprocessing-fork' in sys.argv: # We are using the multiprocessing module on windows to launch a # worker process from multiprocessing import freeze_support freeze_support() return 0 # Close open file descriptors inherited from parent # On Unix this is done by the subprocess module os.closerange(3, 256) if isosx and 'CALIBRE_WORKER_ADDRESS' not in os.environ and 'CALIBRE_SIMPLE_WORKER' not in os.environ and '--pipe-worker' not in sys.argv: # On some OS X computers launchd apparently tries to # launch the last run process from the bundle # so launch the gui as usual from calibre.gui2.main import main as gui_main return gui_main(['calibre']) csw = os.environ.get('CALIBRE_SIMPLE_WORKER', None) if csw: mod, _, func = csw.partition(':') mod = importlib.import_module(mod) func = getattr(mod, func) func() return if '--pipe-worker' in sys.argv: try: exec(sys.argv[-1]) except Exception: print('Failed to run pipe worker with command:', sys.argv[-1]) raise return address = msgpack_loads(from_hex_bytes(os.environ['CALIBRE_WORKER_ADDRESS'])) key = from_hex_bytes(os.environ['CALIBRE_WORKER_KEY']) resultf = from_hex_unicode(os.environ['CALIBRE_WORKER_RESULT']) with closing(Client(address, authkey=key)) as conn: name, args, kwargs, desc = eintr_retry_call(conn.recv) if desc: prints(desc) sys.stdout.flush() func, notification = get_func(name) notifier = Progress(conn) if notification: kwargs[notification] = notifier notifier.start() result = func(*args, **kwargs) if result is not None and os.path.exists(os.path.dirname(resultf)): with lopen(resultf, 'wb') as f: f.write(pickle_dumps(result)) notifier.queue.put(None) try: sys.stdout.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) try: sys.stderr.flush() except EnvironmentError: pass # Happens sometimes on OS X for GUI processes (EPIPE) return 0