home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_1150 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  51.1 KB  |  1,243 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. __license__ = 'GPL v3'
  5. __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
  6. __docformat__ = 'restructuredtext en'
  7. import shutil
  8. import os
  9. import datetime
  10. import time
  11. from functools import partial
  12. from PyQt4.Qt import QInputDialog, pyqtSignal, QModelIndex, QThread, Qt, SIGNAL, QPixmap, QTimer, QDialog
  13. from calibre import strftime
  14. from calibre.ptempfile import PersistentTemporaryFile
  15. from calibre.utils.config import prefs, dynamic
  16. from calibre.gui2 import error_dialog, Dispatcher, gprefs, choose_files, choose_dir, warning_dialog, info_dialog, question_dialog, config, open_local_file
  17. from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
  18. from calibre.utils.filenames import ascii_filename
  19. from calibre.gui2.widgets import IMAGE_EXTENSIONS
  20. from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
  21. from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
  22. from calibre.gui2.dialogs.tag_list_editor import TagListEditor
  23. from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, fetch_scheduled_recipe, generate_catalog
  24. from calibre.constants import preferred_encoding, filesystem_encoding, isosx
  25. from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
  26. from calibre.ebooks import BOOK_EXTENSIONS
  27. from calibre.gui2.dialogs.confirm_delete import confirm
  28. from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog
  29.  
  30. class AnnotationsAction(object):
  31.     
  32.     def fetch_annotations(self, *args):
  33.         
  34.         def get_ids_from_selected_rows():
  35.             rows = self.library_view.selectionModel().selectedRows()
  36.             if not rows or len(rows) < 2:
  37.                 rows = xrange(self.library_view.model().rowCount(QModelIndex()))
  38.             
  39.             ids = map(self.library_view.model().id, rows)
  40.             return ids
  41.  
  42.         
  43.         def get_formats(id):
  44.             formats = db.formats(id, index_is_id = True)
  45.             fmts = []
  46.             if formats:
  47.                 for format in formats.split(','):
  48.                     fmts.append(format.lower())
  49.                 
  50.             
  51.             return fmts
  52.  
  53.         
  54.         def generate_annotation_paths(ids, db, device):
  55.             path_map = { }
  56.             for id in ids:
  57.                 mi = db.get_metadata(id, index_is_id = True)
  58.                 a_path = device.create_upload_path(os.path.abspath('/<storage>'), mi, 'x.bookmark', create_dirs = False)
  59.                 path_map[id] = dict(path = a_path, fmts = get_formats(id))
  60.             
  61.             return path_map
  62.  
  63.         device = self.device_manager.device
  64.         if self.current_view() is not self.library_view:
  65.             return error_dialog(self, _('Use library only'), _('User annotations generated from main library only'), show = True)
  66.         db = self.library_view.model().db
  67.         ids = get_ids_from_selected_rows()
  68.         if not ids:
  69.             return error_dialog(self, _('No books selected'), _('No books selected to fetch annotations from'), show = True)
  70.         path_map = generate_annotation_paths(ids, db, device)
  71.         self.device_manager.annotations(Dispatcher(self.annotations_fetched), path_map)
  72.  
  73.     
  74.     def annotations_fetched(self, job):
  75.         Device = Device
  76.         import calibre.devices.usbms.device
  77.         MetaInformation = MetaInformation
  78.         import calibre.ebooks.metadata
  79.         ProgressDialog = ProgressDialog
  80.         import calibre.gui2.dialogs.progress
  81.         do_add_format = do_add_format
  82.         import calibre.library.cli
  83.         
  84.         class Updater(None, None, None, None, 'Updater', (QThread,)):
  85.             update_progress = pyqtSignal(int)
  86.             update_done = pyqtSignal()
  87.             FINISHED_READING_PCT_THRESHOLD = 96
  88.             
  89.             def __init__(self, parent, db, annotation_map, done_callback):
  90.                 QThread.__init__(self, parent)
  91.                 self.db = db
  92.                 self.pd = ProgressDialog(_('Merging user annotations into database'), '', 0, len(job.result), parent = parent)
  93.                 self.am = annotation_map
  94.                 self.done_callback = done_callback
  95.                 self.connect(self.pd, SIGNAL('canceled()'), self.canceled)
  96.                 self.pd.setModal(True)
  97.                 self.pd.show()
  98.                 self.update_progress.connect(self.pd.set_value, type = Qt.QueuedConnection)
  99.                 self.update_done.connect(self.pd.hide, type = Qt.QueuedConnection)
  100.  
  101.             
  102.             def generate_annotation_html(self, bookmark):
  103.                 last_read_location = bookmark.last_read_location
  104.                 timestamp = datetime.datetime.utcfromtimestamp(bookmark.timestamp)
  105.                 percent_read = bookmark.percent_read
  106.                 ka_soup = BeautifulSoup()
  107.                 dtc = 0
  108.                 divTag = Tag(ka_soup, 'div')
  109.                 divTag['class'] = 'user_annotations'
  110.                 spanTag = Tag(ka_soup, 'span')
  111.                 spanTag['style'] = 'font-weight:bold'
  112.                 if bookmark.book_format == 'pdf':
  113.                     spanTag.insert(0, NavigableString(_('%s<br />Last Page Read: %d (%d%%)') % (strftime(u'%x', timestamp.timetuple()), last_read_location, percent_read)))
  114.                 else:
  115.                     spanTag.insert(0, NavigableString(_('%s<br />Last Page Read: Location %d (%d%%)') % (strftime(u'%x', timestamp.timetuple()), last_read_location, percent_read)))
  116.                 divTag.insert(dtc, spanTag)
  117.                 dtc += 1
  118.                 divTag.insert(dtc, Tag(ka_soup, 'br'))
  119.                 dtc += 1
  120.                 if bookmark.user_notes:
  121.                     user_notes = bookmark.user_notes
  122.                     annotations = []
  123.                     for location in sorted(user_notes):
  124.                         if user_notes[location]['text']:
  125.                             None(annotations.append % (_('<b>Location %d • %s</b><br />%s<br />'), user_notes[location]['displayed_location'], user_notes[location]['type'] if user_notes[location]['type'] == 'Note' else '<i>%s</i>' % user_notes[location]['text']))
  126.                             continue
  127.                         if bookmark.book_format == 'pdf':
  128.                             annotations.append(_('<b>Page %d • %s</b><br />') % (user_notes[location]['displayed_location'], user_notes[location]['type']))
  129.                             continue
  130.                         annotations.append(_('<b>Location %d • %s</b><br />') % (user_notes[location]['displayed_location'], user_notes[location]['type']))
  131.                     
  132.                     for annotation in annotations:
  133.                         divTag.insert(dtc, annotation)
  134.                         dtc += 1
  135.                     
  136.                 
  137.                 ka_soup.insert(0, divTag)
  138.                 return ka_soup
  139.  
  140.             
  141.             def mark_book_as_read(self, id):
  142.                 read_tag = gprefs.get('catalog_epub_mobi_read_tag')
  143.                 if read_tag:
  144.                     self.db.set_tags(id, [
  145.                         read_tag], append = True)
  146.                 
  147.  
  148.             
  149.             def canceled(self):
  150.                 self.pd.hide()
  151.  
  152.             
  153.             def run(self):
  154.                 ignore_tags = set([
  155.                     'Catalog',
  156.                     'Clippings'])
  157.                 for i, id in enumerate(self.am):
  158.                     bm = Device.UserAnnotation(self.am[id][0], self.am[id][1])
  159.                     if bm.type == 'kindle_bookmark':
  160.                         mi = self.db.get_metadata(id, index_is_id = True)
  161.                         user_notes_soup = self.generate_annotation_html(bm.value)
  162.                         if mi.comments:
  163.                             a_offset = mi.comments.find('<div class="user_annotations">')
  164.                             ad_offset = mi.comments.find('<hr class="annotations_divider" />')
  165.                             if a_offset >= 0:
  166.                                 mi.comments = mi.comments[:a_offset]
  167.                             
  168.                             if ad_offset >= 0:
  169.                                 mi.comments = mi.comments[:ad_offset]
  170.                             
  171.                             if set(mi.tags).intersection(ignore_tags):
  172.                                 continue
  173.                             
  174.                             if mi.comments:
  175.                                 hrTag = Tag(user_notes_soup, 'hr')
  176.                                 hrTag['class'] = 'annotations_divider'
  177.                                 user_notes_soup.insert(0, hrTag)
  178.                             
  179.                             mi.comments += user_notes_soup.prettify()
  180.                         else:
  181.                             mi.comments = unicode(user_notes_soup.prettify())
  182.                         self.db.set_comment(id, mi.comments)
  183.                         if bm.value.percent_read >= self.FINISHED_READING_PCT_THRESHOLD:
  184.                             if not set(mi.tags).intersection(ignore_tags):
  185.                                 self.mark_book_as_read(id)
  186.                             
  187.                         
  188.                         self.db.add_format_with_hooks(id, bm.value.bookmark_extension, bm.value.path, index_is_id = True)
  189.                         self.update_progress.emit(i)
  190.                         continue
  191.                     if bm.type == 'kindle_clippings':
  192.                         last_update = 'Last modified %s' % strftime(u'%x %X', bm.value['timestamp'].timetuple())
  193.                         mc_id = list(db.data.parse('title:"My Clippings"'))
  194.                         if mc_id:
  195.                             do_add_format(self.db, mc_id[0], 'TXT', bm.value['path'])
  196.                             mi = self.db.get_metadata(mc_id[0], index_is_id = True)
  197.                             mi.comments = last_update
  198.                             self.db.set_metadata(mc_id[0], mi)
  199.                         else:
  200.                             mi = MetaInformation('My Clippings', authors = [
  201.                                 'Kindle'])
  202.                             mi.tags = [
  203.                                 'Clippings']
  204.                             mi.comments = last_update
  205.                             self.db.add_books([
  206.                                 bm.value['path']], [
  207.                                 'txt'], [
  208.                                 mi])
  209.                     mc_id
  210.                 
  211.                 self.update_done.emit()
  212.                 self.done_callback(self.am.keys())
  213.  
  214.  
  215.         if not job.result:
  216.             return None
  217.         if self.current_view() is not self.library_view:
  218.             return error_dialog(self, _('Use library only'), _('User annotations generated from main library only'), show = True)
  219.         db = self.library_view.model().db
  220.         self._AnnotationsAction__annotation_updater = Updater(self, db, job.result, Dispatcher(self.library_view.model().refresh_ids))
  221.         self._AnnotationsAction__annotation_updater.start()
  222.  
  223.  
  224.  
  225. class AddAction(object):
  226.     
  227.     def __init__(self):
  228.         self._add_filesystem_book = Dispatcher(self._AddAction__add_filesystem_book)
  229.  
  230.     
  231.     def add_recursive(self, single):
  232.         root = choose_dir(self, 'recursive book import root dir dialog', 'Select root folder')
  233.         if not root:
  234.             return None
  235.         Adder = Adder
  236.         import calibre.gui2.add
  237.         self._adder = Adder(self, self.library_view.model().db, Dispatcher(self._files_added), spare_server = self.spare_server)
  238.         self._adder.add_recursive(root, single)
  239.  
  240.     
  241.     def add_recursive_single(self, *args):
  242.         self.add_recursive(True)
  243.  
  244.     
  245.     def add_recursive_multiple(self, *args):
  246.         self.add_recursive(False)
  247.  
  248.     
  249.     def add_empty(self, *args):
  250.         (num, ok) = QInputDialog.getInt(self, _('How many empty books?'), _('How many empty books should be added?'), 1, 1, 100)
  251.         if ok:
  252.             MetaInformation = MetaInformation
  253.             import calibre.ebooks.metadata
  254.             for x in xrange(num):
  255.                 self.library_view.model().db.import_book(MetaInformation(None), [])
  256.             
  257.             self.library_view.model().books_added(num)
  258.         
  259.  
  260.     
  261.     def files_dropped(self, paths):
  262.         to_device = self.stack.currentIndex() != 0
  263.         self._add_books(paths, to_device)
  264.  
  265.     
  266.     def files_dropped_on_book(self, event, paths):
  267.         accept = False
  268.         if self.current_view() is not self.library_view:
  269.             return None
  270.         db = self.library_view.model().db
  271.         current_idx = self.library_view.currentIndex()
  272.         if not current_idx.isValid():
  273.             return None
  274.         cid = db.id(current_idx.row())
  275.         for path in paths:
  276.             ext = os.path.splitext(path)[1].lower()
  277.             if ext in IMAGE_EXTENSIONS:
  278.                 pmap = QPixmap()
  279.                 pmap.load(path)
  280.                 if not pmap.isNull():
  281.                     accept = True
  282.                     db.set_cover(cid, pmap)
  283.                 
  284.             pmap.isNull()
  285.             if ext in BOOK_EXTENSIONS:
  286.                 db.add_format_with_hooks(cid, ext, path, index_is_id = True)
  287.                 accept = True
  288.                 continue
  289.             None if ext else self.current_view() is not self.library_view
  290.         
  291.         if accept:
  292.             event.accept()
  293.             self.library_view.model().current_changed(current_idx, current_idx)
  294.         
  295.  
  296.     
  297.     def __add_filesystem_book(self, paths, allow_device = True):
  298.         if isinstance(paths, basestring):
  299.             paths = [
  300.                 paths]
  301.         
  302.         books = _[1]
  303.  
  304.     
  305.     def add_filesystem_book(self, paths, allow_device = True):
  306.         self._add_filesystem_book(paths, allow_device = allow_device)
  307.  
  308.     
  309.     def add_books(self, *args):
  310.         filters = [
  311.             (_('Books'), BOOK_EXTENSIONS),
  312.             (_('EPUB Books'), [
  313.                 'epub']),
  314.             (_('LRF Books'), [
  315.                 'lrf']),
  316.             (_('HTML Books'), [
  317.                 'htm',
  318.                 'html',
  319.                 'xhtm',
  320.                 'xhtml']),
  321.             (_('LIT Books'), [
  322.                 'lit']),
  323.             (_('MOBI Books'), [
  324.                 'mobi',
  325.                 'prc',
  326.                 'azw']),
  327.             (_('Topaz books'), [
  328.                 'tpz',
  329.                 'azw1']),
  330.             (_('Text books'), [
  331.                 'txt',
  332.                 'rtf']),
  333.             (_('PDF Books'), [
  334.                 'pdf']),
  335.             (_('Comics'), [
  336.                 'cbz',
  337.                 'cbr',
  338.                 'cbc']),
  339.             (_('Archives'), [
  340.                 'zip',
  341.                 'rar'])]
  342.         to_device = self.stack.currentIndex() != 0
  343.         if to_device:
  344.             filters = [
  345.                 (_('Supported books'), self.device_manager.device.FORMATS)]
  346.         
  347.         books = choose_files(self, 'add books dialog dir', 'Select books', filters = filters)
  348.         if not books:
  349.             return None
  350.         self._add_books(books, to_device)
  351.  
  352.     
  353.     def _add_books(self, paths, to_device, on_card = None):
  354.         if on_card is None:
  355.             if self.stack.currentIndex() == 2:
  356.                 pass
  357.             elif self.stack.currentIndex() == 3:
  358.                 pass
  359.             
  360.             on_card = None
  361.         
  362.         if not paths:
  363.             return None
  364.         Adder = Adder
  365.         import calibre.gui2.add
  366.         self._AddAction__adder_func = partial(self._files_added, on_card = on_card)
  367.         self._adder = paths(Adder, self if to_device else self.library_view.model().db, Dispatcher(self._AddAction__adder_func), spare_server = self.spare_server)
  368.         self._adder.add(paths)
  369.  
  370.     
  371.     def _files_added(self, paths = [], names = [], infos = [], on_card = None):
  372.         if paths:
  373.             self.upload_books(paths, list(map(ascii_filename, names)), infos, on_card = on_card)
  374.             self.status_bar.show_message(_('Uploading books to device.'), 2000)
  375.         
  376.         if getattr(self._adder, 'number_of_books_added', 0) > 0:
  377.             self.library_view.model().books_added(self._adder.number_of_books_added)
  378.             if hasattr(self, 'db_images'):
  379.                 self.db_images.reset()
  380.             
  381.         
  382.         if getattr(self._adder, 'merged_books', False):
  383.             books = []([ _[1] if isinstance(x, unicode) else x.decode(preferred_encoding, 'replace') for x in self._adder.merged_books ])
  384.             info_dialog(self, _('Merged some books'), _('Some duplicates were found and merged into the following existing books:'), det_msg = books, show = True)
  385.         
  386.         if getattr(self._adder, 'critical', None):
  387.             det_msg = []
  388.             for name, log in self._adder.critical.items():
  389.                 if isinstance(name, str):
  390.                     name = name.decode(filesystem_encoding, 'replace')
  391.                 
  392.                 det_msg.append(name + '\n' + log)
  393.             
  394.             warning_dialog(self, _('Failed to read metadata'), _('Failed to read metadata from the following') + ':', det_msg = '\n\n'.join(det_msg), show = True)
  395.         
  396.         if hasattr(self._adder, 'cleanup'):
  397.             self._adder.cleanup()
  398.         
  399.         self._adder = None
  400.  
  401.     
  402.     def _add_from_device_adder(self, paths = [], names = [], infos = [], on_card = None, model = None):
  403.         self._files_added(paths, names, infos, on_card = on_card)
  404.         self.set_books_in_library(booklists = [
  405.             model.db], reset = True)
  406.         model.reset()
  407.  
  408.     
  409.     def add_books_from_device(self, view):
  410.         rows = view.selectionModel().selectedRows()
  411.         if not rows or len(rows) == 0:
  412.             d = error_dialog(self, _('Add to library'), _('No book selected'))
  413.             d.exec_()
  414.             return None
  415.         paths = _[1]
  416.         ve = self.device_manager.device.VIRTUAL_BOOK_EXTENSIONS
  417.         
  418.         def ext(x):
  419.             ans = os.path.splitext(x)[1]
  420.             ans = None if len(ans) > 1 else ans
  421.             return ans.lower()
  422.  
  423.         remove = [](_[2])
  424.         if not paths or len(paths) == 0:
  425.             d = error_dialog(self, _('Add to library'), _('No book files found'))
  426.             d.exec_()
  427.             return None
  428.         Adder = Adder
  429.         import calibre.gui2.add
  430.         self._AddAction__adder_func = partial(self._add_from_device_adder, on_card = None, model = view._model)
  431.         self._adder = Adder(self, self.library_view.model().db, Dispatcher(self._AddAction__adder_func), spare_server = self.spare_server)
  432.         self._adder.add(paths)
  433.  
  434.  
  435.  
  436. class DeleteAction(object):
  437.     
  438.     def _get_selected_formats(self, msg):
  439.         SelectFormats = SelectFormats
  440.         import calibre.gui2.dialogs.select_formats
  441.         fmts = self.library_view.model().db.all_formats()
  442.         d = []([ x.lower() for x in fmts ], msg, parent = self)
  443.         if d.exec_() != d.Accepted:
  444.             return None
  445.         return d.selected_formats
  446.  
  447.     
  448.     def _get_selected_ids(self, err_title = _('Cannot delete')):
  449.         rows = self.library_view.selectionModel().selectedRows()
  450.         if not rows or len(rows) == 0:
  451.             d = error_dialog(self, err_title, _('No book selected'))
  452.             d.exec_()
  453.             return set([])
  454.         return set(map(self.library_view.model().id, rows))
  455.  
  456.     
  457.     def delete_selected_formats(self, *args):
  458.         ids = self._get_selected_ids()
  459.         if not ids:
  460.             return None
  461.         fmts = self._get_selected_formats(_('Choose formats to be deleted'))
  462.         if not fmts:
  463.             return None
  464.         for id in ids:
  465.             for fmt in fmts:
  466.                 self.library_view.model().db.remove_format(id, fmt, index_is_id = True, notify = False)
  467.             
  468.         
  469.         self.library_view.model().refresh_ids(ids)
  470.         self.library_view.model().current_changed(self.library_view.currentIndex(), self.library_view.currentIndex())
  471.  
  472.     
  473.     def delete_all_but_selected_formats(self, *args):
  474.         ids = self._get_selected_ids()
  475.         if not ids:
  476.             return None
  477.         fmts = self._get_selected_formats('<p>' + _('Choose formats <b>not</b> to be deleted'))
  478.         if fmts is None:
  479.             return None
  480.         for id in ids:
  481.             bfmts = self.library_view.model().db.formats(id, index_is_id = True)
  482.             bfmts = []([ x.lower() for x in bfmts.split(',') ])
  483.             rfmts = bfmts - set(fmts)
  484.             for fmt in rfmts:
  485.                 self.library_view.model().db.remove_format(id, fmt, index_is_id = True, notify = False)
  486.             
  487.         
  488.         self.library_view.model().refresh_ids(ids)
  489.         self.library_view.model().current_changed(self.library_view.currentIndex(), self.library_view.currentIndex())
  490.  
  491.     
  492.     def remove_matching_books_from_device(self, *args):
  493.         if not self.device_manager.is_device_connected:
  494.             d = error_dialog(self, _('Cannot delete books'), _('No device is connected'))
  495.             d.exec_()
  496.             return None
  497.         ids = self._get_selected_ids()
  498.         if not ids:
  499.             return None
  500.         to_delete = { }
  501.         some_to_delete = False
  502.         for model, name in ((self.memory_view.model(), _('Main memory')), (self.card_a_view.model(), _('Storage Card A')), (self.card_b_view.model(), _('Storage Card B'))):
  503.             to_delete[name] = (model, model.paths_for_db_ids(ids))
  504.             if len(to_delete[name][1]) > 0:
  505.                 some_to_delete = True
  506.                 continue
  507.             ids
  508.         
  509.         if not some_to_delete:
  510.             d = error_dialog(self, _('No books to delete'), _('None of the selected books are on the device'))
  511.             d.exec_()
  512.             return None
  513.         d = DeleteMatchingFromDeviceDialog(self, to_delete)
  514.         if d.exec_():
  515.             paths = { }
  516.             ids = { }
  517.             for model, id, path in d.result:
  518.                 paths[model].append(path)
  519.                 ids[model].append(id)
  520.             
  521.             for model in paths:
  522.                 job = self.remove_paths(paths[model])
  523.                 self.delete_memory[job] = (paths[model], model)
  524.                 model.mark_for_deletion(job, ids[model], rows_are_ids = True)
  525.             
  526.             self.status_bar.show_message(_('Deleting books from device.'), 1000)
  527.         
  528.  
  529.     
  530.     def delete_covers(self, *args):
  531.         ids = self._get_selected_ids()
  532.         if not ids:
  533.             return None
  534.         for id in ids:
  535.             self.library_view.model().db.remove_cover(id)
  536.         
  537.         self.library_view.model().refresh_ids(ids)
  538.         self.library_view.model().current_changed(self.library_view.currentIndex(), self.library_view.currentIndex())
  539.  
  540.     
  541.     def delete_books(self, *args):
  542.         view = self.current_view()
  543.         rows = view.selectionModel().selectedRows()
  544.         if not rows or len(rows) == 0:
  545.             return None
  546.         if self.stack.currentIndex() == 0:
  547.             if not confirm('<p>' + _('The selected books will be <b>permanently deleted</b> and the files removed from your computer. Are you sure?') + '</p>', 'library_delete_books', self):
  548.                 return None
  549.             ci = view.currentIndex()
  550.             row = None
  551.             ids_deleted = view.model().delete_books(rows)
  552.             for v in (self.memory_view, self.card_a_view, self.card_b_view):
  553.                 if v is None:
  554.                     continue
  555.                 
  556.                 v.model().clear_ondevice(ids_deleted)
  557.             
  558.             if row is not None:
  559.                 ci = view.model().index(row, 0)
  560.                 if ci.isValid():
  561.                     view.set_current_row(row)
  562.                 
  563.             
  564.         elif not confirm('<p>' + _('The selected books will be <b>permanently deleted</b> from your device. Are you sure?') + '</p>', 'device_delete_books', self):
  565.             return None
  566.         if self.stack.currentIndex() == 1:
  567.             view = self.memory_view
  568.         elif self.stack.currentIndex() == 2:
  569.             view = self.card_a_view
  570.         else:
  571.             view = self.card_b_view
  572.         paths = view.model().paths(rows)
  573.         job = self.remove_paths(paths)
  574.         self.delete_memory[job] = (paths, view.model())
  575.         view.model().mark_for_deletion(job, rows)
  576.         self.status_bar.show_message(_('Deleting books from device.'), 1000)
  577.  
  578.  
  579.  
  580. class EditMetadataAction(object):
  581.     
  582.     def download_metadata(self, checked, covers = True, set_metadata = True, set_social_metadata = None):
  583.         rows = self.library_view.selectionModel().selectedRows()
  584.         if not rows or len(rows) == 0:
  585.             d = error_dialog(self, _('Cannot download metadata'), _('No books selected'))
  586.             d.exec_()
  587.             return None
  588.         db = self.library_view.model().db
  589.         ids = [ db.id(row.row()) for row in rows ]
  590.         DownloadMetadata = DownloadMetadata
  591.         import calibre.gui2.metadata
  592.         self._download_book_metadata = DownloadMetadata(db, ids, get_covers = covers, set_metadata = set_metadata, get_social_metadata = get_social_metadata)
  593.         self._download_book_metadata.start()
  594.         if set_social_metadata is not None and set_social_metadata:
  595.             x = _('social metadata')
  596.         elif covers and not set_metadata:
  597.             pass
  598.         
  599.         x = _('metadata')
  600.         self.progress_indicator.start(_('Downloading %s for %d book(s)') % (x, len(ids)))
  601.         self._book_metadata_download_check = QTimer(self)
  602.         self.connect(self._book_metadata_download_check, SIGNAL('timeout()'), self.book_metadata_download_check, Qt.QueuedConnection)
  603.         self._book_metadata_download_check.start(100)
  604.  
  605.     
  606.     def book_metadata_download_check(self):
  607.         if self._download_book_metadata.is_alive():
  608.             return None
  609.         self._book_metadata_download_check.stop()
  610.         self.progress_indicator.stop()
  611.         cr = self.library_view.currentIndex().row()
  612.         x = self._download_book_metadata
  613.         self._download_book_metadata = None
  614.         if x.exception is None:
  615.             self.library_view.model().refresh_ids(x.updated, cr)
  616.             if self.cover_flow:
  617.                 self.cover_flow.dataChanged()
  618.             
  619.             if x.failures:
  620.                 details = [ '%s: %s' % (title, reason) for title, reason in x.failures.values() ]
  621.                 details = '%s\n' % '\n'.join(details)
  622.                 warning_dialog(self, _('Failed to download some metadata'), _('Failed to download metadata for the following:'), det_msg = details).exec_()
  623.             
  624.         else:
  625.             err = _('Failed to download metadata:')
  626.             error_dialog(self, _('Error'), err, det_msg = x.tb).exec_()
  627.  
  628.     
  629.     def edit_metadata(self, checked, bulk = None):
  630.         rows = self.library_view.selectionModel().selectedRows()
  631.         previous = self.library_view.currentIndex()
  632.         if not rows or len(rows) == 0:
  633.             d = error_dialog(self, _('Cannot edit metadata'), _('No books selected'))
  634.             d.exec_()
  635.             return None
  636.         if (bulk or bulk is None) and len(rows) > 1:
  637.             return self.edit_bulk_metadata(checked)
  638.         
  639.         def accepted(id):
  640.             self.library_view.model().refresh_ids([
  641.                 id])
  642.  
  643.         for row in rows:
  644.             self._metadata_view_id = self.library_view.model().db.id(row.row())
  645.             d = MetadataSingleDialog(self, row.row(), self.library_view.model().db, accepted_callback = accepted, cancel_all = rows.index(row) < len(rows) - 1)
  646.             self.connect(d, SIGNAL('view_format(PyQt_PyObject)'), self.metadata_view_format)
  647.             d.exec_()
  648.             if d.cancel_all:
  649.                 break
  650.                 continue
  651.             (len(rows) > 1,)
  652.         
  653.  
  654.     
  655.     def edit_bulk_metadata(self, checked):
  656.         rows = [ r.row() for r in self.library_view.selectionModel().selectedRows() ]
  657.         if not rows or len(rows) == 0:
  658.             d = error_dialog(self, _('Cannot edit metadata'), _('No books selected'))
  659.             d.exec_()
  660.             return None
  661.  
  662.     
  663.     def merge_books(self, safe_merge = False):
  664.         if self.stack.currentIndex() != 0:
  665.             return None
  666.         rows = self.library_view.selectionModel().selectedRows()
  667.         if not rows or len(rows) == 0:
  668.             return error_dialog(self, _('Cannot merge books'), _('No books selected'), show = True)
  669.         if len(rows) < 2:
  670.             return error_dialog(self, _('Cannot merge books'), _('At least two books must be selected for merging'), show = True)
  671.         (dest_id, src_books, src_ids) = self.books_to_merge(rows)
  672.         if safe_merge:
  673.             if not confirm('<p>' + _('All book formats and metadata from the selected books will be added to the <b>first selected book.</b><br><br> The second and subsequently selected books will not be deleted or changed.<br><br>Please confirm you want to proceed.') + '</p>', 'merge_books_safe', self):
  674.                 return None
  675.             self.add_formats(dest_id, src_books)
  676.             self.merge_metadata(dest_id, src_ids)
  677.         elif not confirm('<p>' + _('All book formats and metadata from the selected books will be merged into the <b>first selected book</b>.<br><br>After merger the second and subsequently selected books will be <b>deleted</b>. <br><br>All book formats of the first selected book will be kept and any duplicate formats in the second and subsequently selected books will be permanently <b>deleted</b> from your computer.<br><br>  Are you <b>sure</b> you want to proceed?') + '</p>', 'merge_books', self):
  678.             return None
  679.         len(rows) < 2
  680.         self.add_formats(dest_id, src_books)
  681.         self.merge_metadata(dest_id, src_ids)
  682.         self.delete_books_after_merge(src_ids)
  683.         dest_row = rows[0].row()
  684.         for row in rows:
  685.             if row.row() < rows[0].row():
  686.                 dest_row -= 1
  687.                 continue
  688.             self.stack.currentIndex() != 0 if len(rows) > 5 else len(rows) == 0
  689.         
  690.         ci = self.library_view.model().index(dest_row, 0)
  691.         if ci.isValid():
  692.             self.library_view.setCurrentIndex(ci)
  693.         
  694.  
  695.     
  696.     def add_formats(self, dest_id, src_books, replace = False):
  697.         for src_book in src_books:
  698.             if src_book:
  699.                 fmt = os.path.splitext(src_book)[-1].replace('.', '').upper()
  700.                 
  701.                 try:
  702.                     f = _[1]
  703.                     self.library_view.model().db.add_format(dest_id, fmt, f, index_is_id = True, notify = False, replace = replace)
  704.                 finally:
  705.                     pass
  706.  
  707.                 continue
  708.             open(src_book, 'rb').__exit__
  709.         
  710.  
  711.     
  712.     def books_to_merge(self, rows):
  713.         src_books = []
  714.         src_ids = []
  715.         m = self.library_view.model()
  716.         for i, row in enumerate(rows):
  717.             id_ = m.id(row)
  718.             if i == 0:
  719.                 dest_id = id_
  720.                 continue
  721.             src_ids.append(id_)
  722.             dbfmts = m.db.formats(id_, index_is_id = True)
  723.             if dbfmts:
  724.                 for fmt in dbfmts.split(','):
  725.                     src_books.append(m.db.format_abspath(id_, fmt, index_is_id = True))
  726.                 
  727.         
  728.         return [
  729.             dest_id,
  730.             src_books,
  731.             src_ids]
  732.  
  733.     
  734.     def delete_books_after_merge(self, ids_to_delete):
  735.         self.library_view.model().delete_books_by_id(ids_to_delete)
  736.  
  737.     
  738.     def merge_metadata(self, dest_id, src_ids):
  739.         db = self.library_view.model().db
  740.         dest_mi = db.get_metadata(dest_id, index_is_id = True, get_cover = True)
  741.         orig_dest_comments = dest_mi.comments
  742.         for src_id in src_ids:
  743.             src_mi = db.get_metadata(src_id, index_is_id = True, get_cover = True)
  744.             if src_mi.comments and orig_dest_comments != src_mi.comments:
  745.                 if not dest_mi.comments:
  746.                     dest_mi.comments = src_mi.comments
  747.                 else:
  748.                     dest_mi.comments = unicode(dest_mi.comments) + u'\n\n' + unicode(src_mi.comments)
  749.             
  750.             if src_mi.title:
  751.                 if not (dest_mi.title) or dest_mi.title == _('Unknown'):
  752.                     dest_mi.title = src_mi.title
  753.                 
  754.             if src_mi.title:
  755.                 if not (dest_mi.authors) or dest_mi.authors[0] == _('Unknown'):
  756.                     dest_mi.authors = src_mi.authors
  757.                     dest_mi.author_sort = src_mi.author_sort
  758.                 
  759.             if src_mi.tags:
  760.                 if not dest_mi.tags:
  761.                     dest_mi.tags = src_mi.tags
  762.                 else:
  763.                     dest_mi.tags.extend(src_mi.tags)
  764.             
  765.             if src_mi.cover and not (dest_mi.cover):
  766.                 dest_mi.cover = src_mi.cover
  767.             
  768.             if not dest_mi.publisher:
  769.                 dest_mi.publisher = src_mi.publisher
  770.             
  771.             if not dest_mi.rating:
  772.                 dest_mi.rating = src_mi.rating
  773.             
  774.             if not dest_mi.series:
  775.                 dest_mi.series = src_mi.series
  776.                 dest_mi.series_index = src_mi.series_index
  777.                 continue
  778.         
  779.         db.set_metadata(dest_id, dest_mi, ignore_errors = False)
  780.         for key in db.field_metadata:
  781.             if db.field_metadata[key]['is_custom']:
  782.                 colnum = db.field_metadata[key]['colnum']
  783.                 if db.field_metadata[key]['datatype'] == 'comments':
  784.                     orig_dest_value = db.get_custom(dest_id, num = colnum, index_is_id = True)
  785.                 
  786.                 for src_id in src_ids:
  787.                     dest_value = db.get_custom(dest_id, num = colnum, index_is_id = True)
  788.                     src_value = db.get_custom(src_id, num = colnum, index_is_id = True)
  789.                     if db.field_metadata[key]['datatype'] == 'comments':
  790.                         if src_value and src_value != orig_dest_value:
  791.                             if not dest_value:
  792.                                 db.set_custom(dest_id, src_value, num = colnum)
  793.                             else:
  794.                                 dest_value = unicode(dest_value) + u'\n\n' + unicode(src_value)
  795.                                 db.set_custom(dest_id, dest_value, num = colnum)
  796.                         
  797.                     
  798.                     if db.field_metadata[key]['datatype'] in ('bool', 'int', 'float', 'rating', 'datetime') and not dest_value:
  799.                         db.set_custom(dest_id, src_value, num = colnum)
  800.                     
  801.                     if db.field_metadata[key]['datatype'] == 'series' and not dest_value:
  802.                         if src_value:
  803.                             src_index = db.get_custom_extra(src_id, num = colnum, index_is_id = True)
  804.                             db.set_custom(dest_id, src_value, num = colnum, extra = src_index)
  805.                         
  806.                     
  807.                     if db.field_metadata[key]['datatype'] == 'text' and not db.field_metadata[key]['is_multiple'] and not dest_value:
  808.                         db.set_custom(dest_id, src_value, num = colnum)
  809.                     
  810.                     if db.field_metadata[key]['datatype'] == 'text' and db.field_metadata[key]['is_multiple']:
  811.                         if src_value:
  812.                             if not dest_value:
  813.                                 dest_value = src_value
  814.                             else:
  815.                                 dest_value.extend(src_value)
  816.                             db.set_custom(dest_id, dest_value, num = colnum)
  817.                         
  818.                     src_value
  819.                 
  820.         
  821.  
  822.     
  823.     def edit_device_collections(self, view, oncard = None):
  824.         model = view.model()
  825.         result = model.get_collections_with_ids()
  826.         
  827.         compare = lambda x, y: cmp(x.lower(), y.lower())
  828.         d = TagListEditor(self, tag_to_match = None, data = result, compare = compare)
  829.         d.exec_()
  830.         if d.result() == d.Accepted:
  831.             to_rename = d.to_rename
  832.             to_delete = d.to_delete
  833.             for text in to_rename:
  834.                 for old_id in to_rename[text]:
  835.                     model.rename_collection(old_id, new_name = unicode(text))
  836.                 
  837.             
  838.             for item in to_delete:
  839.                 model.delete_collection_using_id(item)
  840.             
  841.             self.upload_collections(model.db, view = view, oncard = oncard)
  842.             view.reset()
  843.         
  844.  
  845.  
  846.  
  847. class SaveToDiskAction(object):
  848.     
  849.     def save_single_format_to_disk(self, checked):
  850.         self.save_to_disk(checked, False, prefs['output_format'])
  851.  
  852.     
  853.     def save_specific_format_disk(self, fmt):
  854.         self.save_to_disk(False, False, fmt)
  855.  
  856.     
  857.     def save_to_single_dir(self, checked):
  858.         self.save_to_disk(checked, True)
  859.  
  860.     
  861.     def save_single_fmt_to_single_dir(self, *args):
  862.         self.save_to_disk(False, single_dir = True, single_format = prefs['output_format'])
  863.  
  864.     
  865.     def save_to_disk(self, checked, single_dir = False, single_format = None):
  866.         rows = self.current_view().selectionModel().selectedRows()
  867.         if not rows or len(rows) == 0:
  868.             return error_dialog(self, _('Cannot save to disk'), _('No books selected'), show = True)
  869.         path = choose_dir(self, 'save to disk dialog', _('Choose destination directory'))
  870.         if not path:
  871.             return None
  872.         dpath = os.path.abspath(path).replace('/', os.sep)
  873.         lpath = self.library_view.model().db.library_path.replace('/', os.sep)
  874.         if dpath.startswith(lpath):
  875.             return error_dialog(self, _('Not allowed'), _('You are tying to save files into the calibre library. This can cause corruption of your library. Save to disk is meant to export files from your calibre library elsewhere.'), show = True)
  876.  
  877.     
  878.     def _books_saved(self, path, failures, error):
  879.         self._saver = None
  880.         if error:
  881.             return error_dialog(self, _('Error while saving'), _('There was an error while saving.'), error, show = True)
  882.         open_local_file(path)
  883.  
  884.     
  885.     def books_saved(self, job):
  886.         if job.failed:
  887.             return self.device_job_exception(job)
  888.  
  889.  
  890.  
  891. class GenerateCatalogAction(object):
  892.     
  893.     def generate_catalog(self):
  894.         rows = self.library_view.selectionModel().selectedRows()
  895.         if not rows or len(rows) < 2:
  896.             rows = xrange(self.library_view.model().rowCount(QModelIndex()))
  897.         
  898.         ids = map(self.library_view.model().id, rows)
  899.         dbspec = None
  900.         if not ids:
  901.             return error_dialog(self, _('No books selected'), _('No books selected to generate catalog for'), show = True)
  902.         ret = generate_catalog(self, dbspec, ids, self.device_manager.device)
  903.         if ret is None:
  904.             return None
  905.         (func, args, desc, out, sync, title) = ret
  906.         fmt = os.path.splitext(out)[1][1:].upper()
  907.         job = self.job_manager.run_job(Dispatcher(self.catalog_generated), func, args = args, description = desc)
  908.         job.catalog_file_path = out
  909.         job.fmt = fmt
  910.         job.catalog_sync = sync
  911.         job.catalog_title = title
  912.         self.status_bar.show_message(_('Generating %s catalog...') % fmt)
  913.  
  914.     
  915.     def catalog_generated(self, job):
  916.         if job.result:
  917.             return error_dialog(self, _('No books found'), _('No books to catalog\nCheck exclude tags'), show = True)
  918.         if job.failed:
  919.             return self.job_exception(job)
  920.         id = self.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title)
  921.         self.library_view.model().reset()
  922.         self.status_bar.show_message(_('Catalog generated.'), 3000)
  923.         self.sync_catalogs()
  924.         if job.fmt not in ('EPUB', 'MOBI'):
  925.             export_dir = choose_dir(self, _('Export Catalog Directory'), _('Select destination for %s.%s') % (job.catalog_title, job.fmt.lower()))
  926.             if export_dir:
  927.                 destination = os.path.join(export_dir, '%s.%s' % (job.catalog_title, job.fmt.lower()))
  928.                 shutil.copyfile(job.catalog_file_path, destination)
  929.             
  930.         
  931.  
  932.  
  933.  
  934. class FetchNewsAction(object):
  935.     
  936.     def download_scheduled_recipe(self, arg):
  937.         (func, args, desc, fmt, temp_files) = fetch_scheduled_recipe(arg)
  938.         job = self.job_manager.run_job(Dispatcher(self.scheduled_recipe_fetched), func, args = args, description = desc)
  939.         self.conversion_jobs[job] = (temp_files, fmt, arg)
  940.         self.status_bar.show_message(_('Fetching news from ') + arg['title'], 2000)
  941.  
  942.     
  943.     def scheduled_recipe_fetched(self, job):
  944.         (temp_files, fmt, arg) = self.conversion_jobs.pop(job)
  945.         pt = temp_files[0]
  946.         if job.failed:
  947.             self.scheduler.recipe_download_failed(arg)
  948.             return self.job_exception(job)
  949.         id = self.library_view.model().add_news(pt.name, arg)
  950.         self.library_view.model().reset()
  951.         sync = dynamic.get('news_to_be_synced', set([]))
  952.         sync.add(id)
  953.         dynamic.set('news_to_be_synced', sync)
  954.         self.scheduler.recipe_downloaded(arg)
  955.         self.status_bar.show_message(arg['title'] + _(' fetched.'), 3000)
  956.         self.email_news(id)
  957.         self.sync_news()
  958.  
  959.  
  960.  
  961. class ConvertAction(object):
  962.     
  963.     def auto_convert(self, book_ids, on_card, format):
  964.         previous = self.library_view.currentIndex()
  965.         rows = [ x.row() for x in self.library_view.selectionModel().selectedRows() ]
  966.         (jobs, changed, bad) = convert_single_ebook(self, self.library_view.model().db, book_ids, True, format)
  967.         if jobs == []:
  968.             return None
  969.         self.queue_convert_jobs(jobs, changed, bad, rows, previous, self.book_auto_converted, extra_job_args = [
  970.             on_card])
  971.  
  972.     
  973.     def auto_convert_mail(self, to, fmts, delete_from_library, book_ids, format):
  974.         previous = self.library_view.currentIndex()
  975.         rows = [ x.row() for x in self.library_view.selectionModel().selectedRows() ]
  976.         (jobs, changed, bad) = convert_single_ebook(self, self.library_view.model().db, book_ids, True, format)
  977.         if jobs == []:
  978.             return None
  979.         self.queue_convert_jobs(jobs, changed, bad, rows, previous, self.book_auto_converted_mail, extra_job_args = [
  980.             delete_from_library,
  981.             to,
  982.             fmts])
  983.  
  984.     
  985.     def auto_convert_news(self, book_ids, format):
  986.         previous = self.library_view.currentIndex()
  987.         rows = [ x.row() for x in self.library_view.selectionModel().selectedRows() ]
  988.         (jobs, changed, bad) = convert_single_ebook(self, self.library_view.model().db, book_ids, True, format)
  989.         if jobs == []:
  990.             return None
  991.         self.queue_convert_jobs(jobs, changed, bad, rows, previous, self.book_auto_converted_news)
  992.  
  993.     
  994.     def auto_convert_catalogs(self, book_ids, format):
  995.         previous = self.library_view.currentIndex()
  996.         rows = [ x.row() for x in self.library_view.selectionModel().selectedRows() ]
  997.         (jobs, changed, bad) = convert_single_ebook(self, self.library_view.model().db, book_ids, True, format)
  998.         if jobs == []:
  999.             return None
  1000.         self.queue_convert_jobs(jobs, changed, bad, rows, previous, self.book_auto_converted_catalogs)
  1001.  
  1002.     
  1003.     def get_books_for_conversion(self):
  1004.         rows = [ r.row() for r in self.library_view.selectionModel().selectedRows() ]
  1005.         if not rows or len(rows) == 0:
  1006.             d = error_dialog(self, _('Cannot convert'), _('No books selected'))
  1007.             d.exec_()
  1008.             return None
  1009.         return [ self.library_view.model().db.id(r) for r in rows ]
  1010.  
  1011.     
  1012.     def convert_ebook(self, checked, bulk = None):
  1013.         book_ids = self.get_books_for_conversion()
  1014.         if book_ids is None:
  1015.             return None
  1016.         previous = self.library_view.currentIndex()
  1017.         rows = [ x.row() for x in self.library_view.selectionModel().selectedRows() ]
  1018.         num = 0
  1019.  
  1020.     
  1021.     def queue_convert_jobs(self, jobs, changed, bad, rows, previous, converted_func, extra_job_args = []):
  1022.         for func, args, desc, fmt, id, temp_files in jobs:
  1023.             if id not in bad:
  1024.                 job = self.job_manager.run_job(Dispatcher(converted_func), func, args = args, description = desc)
  1025.                 args = [
  1026.                     temp_files,
  1027.                     fmt,
  1028.                     id] + extra_job_args
  1029.                 self.conversion_jobs[job] = tuple(args)
  1030.                 continue
  1031.         
  1032.         if changed:
  1033.             self.library_view.model().refresh_rows(rows)
  1034.             current = self.library_view.currentIndex()
  1035.             self.library_view.model().current_changed(current, previous)
  1036.         
  1037.  
  1038.     
  1039.     def book_auto_converted(self, job):
  1040.         (temp_files, fmt, book_id, on_card) = self.conversion_jobs[job]
  1041.         self.book_converted(job)
  1042.         self.sync_to_device(on_card, False, specific_format = fmt, send_ids = [
  1043.             book_id], do_auto_convert = False)
  1044.  
  1045.     
  1046.     def book_auto_converted_mail(self, job):
  1047.         (temp_files, fmt, book_id, delete_from_library, to, fmts) = self.conversion_jobs[job]
  1048.         self.book_converted(job)
  1049.         self.send_by_mail(to, fmts, delete_from_library, specific_format = fmt, send_ids = [
  1050.             book_id], do_auto_convert = False)
  1051.  
  1052.     
  1053.     def book_auto_converted_news(self, job):
  1054.         (temp_files, fmt, book_id) = self.conversion_jobs[job]
  1055.         self.book_converted(job)
  1056.         self.sync_news(send_ids = [
  1057.             book_id], do_auto_convert = False)
  1058.  
  1059.     
  1060.     def book_auto_converted_catalogs(self, job):
  1061.         (temp_files, fmt, book_id) = self.conversion_jobs[job]
  1062.         self.book_converted(job)
  1063.         self.sync_catalogs(send_ids = [
  1064.             book_id], do_auto_convert = False)
  1065.  
  1066.     
  1067.     def book_converted(self, job):
  1068.         (temp_files, fmt, book_id) = self.conversion_jobs.pop(job)[:3]
  1069.         
  1070.         try:
  1071.             if job.failed:
  1072.                 self.job_exception(job)
  1073.                 return None
  1074.             data = open(temp_files[-1].name, 'rb')
  1075.             self.library_view.model().db.add_format(book_id, fmt, data, index_is_id = True)
  1076.             data.close()
  1077.             self.status_bar.show_message(job.description + ' completed', 2000)
  1078.         finally:
  1079.             for f in temp_files:
  1080.                 
  1081.                 try:
  1082.                     if os.path.exists(f.name):
  1083.                         os.remove(f.name)
  1084.                 continue
  1085.                 continue
  1086.  
  1087.             
  1088.  
  1089.         self.tags_view.recount()
  1090.         if self.current_view() is self.library_view:
  1091.             current = self.library_view.currentIndex()
  1092.             self.library_view.model().current_changed(current, QModelIndex())
  1093.         
  1094.  
  1095.  
  1096.  
  1097. class ViewAction(object):
  1098.     
  1099.     def view_format(self, row, format):
  1100.         fmt_path = self.library_view.model().db.format_abspath(row, format)
  1101.         if fmt_path:
  1102.             self._view_file(fmt_path)
  1103.         
  1104.  
  1105.     
  1106.     def view_format_by_id(self, id_, format):
  1107.         fmt_path = self.library_view.model().db.format_abspath(id_, format, index_is_id = True)
  1108.         if fmt_path:
  1109.             self._view_file(fmt_path)
  1110.         
  1111.  
  1112.     
  1113.     def metadata_view_format(self, fmt):
  1114.         fmt_path = self.library_view.model().db.format_abspath(self._metadata_view_id, fmt, index_is_id = True)
  1115.         if fmt_path:
  1116.             self._view_file(fmt_path)
  1117.         
  1118.  
  1119.     
  1120.     def book_downloaded_for_viewing(self, job):
  1121.         if job.failed:
  1122.             self.device_job_exception(job)
  1123.             return None
  1124.         self._view_file(job.result)
  1125.  
  1126.     
  1127.     def _launch_viewer(self, name = None, viewer = 'ebook-viewer', internal = True):
  1128.         self.setCursor(Qt.BusyCursor)
  1129.         
  1130.         try:
  1131.             if internal:
  1132.                 args = [
  1133.                     viewer]
  1134.                 if isosx and 'ebook' in viewer:
  1135.                     args.append('--raise-window')
  1136.                 
  1137.                 if name is not None:
  1138.                     args.append(name)
  1139.                 
  1140.                 self.job_manager.launch_gui_app(viewer, kwargs = dict(args = args))
  1141.             else:
  1142.                 open_local_file(name)
  1143.                 time.sleep(2)
  1144.         finally:
  1145.             self.unsetCursor()
  1146.  
  1147.  
  1148.     
  1149.     def _view_file(self, name):
  1150.         ext = os.path.splitext(name)[1].upper().replace('.', '')
  1151.         viewer = None if ext == 'LRF' else 'ebook-viewer'
  1152.         internal = ext in config['internally_viewed_formats']
  1153.         self._launch_viewer(name, viewer, internal)
  1154.  
  1155.     
  1156.     def view_specific_format(self, triggered):
  1157.         rows = self.library_view.selectionModel().selectedRows()
  1158.         if not rows or len(rows) == 0:
  1159.             d = error_dialog(self, _('Cannot view'), _('No book selected'))
  1160.             d.exec_()
  1161.             return None
  1162.         row = rows[0].row()
  1163.         formats = self.library_view.model().db.formats(row).upper().split(',')
  1164.         d = ChooseFormatDialog(self, _('Choose the format to view'), formats)
  1165.         if d.exec_() == QDialog.Accepted:
  1166.             format = d.format()
  1167.             self.view_format(row, format)
  1168.         
  1169.  
  1170.     
  1171.     def _view_check(self, num, max_ = 3):
  1172.         if num <= max_:
  1173.             return True
  1174.         return question_dialog(self, _('Multiple Books Selected'), _('You are attempting to open %d books. Opening too many books at once can be slow and have a negative effect on the responsiveness of your computer. Once started the process cannot be stopped until complete. Do you wish to continue?') % num)
  1175.  
  1176.     
  1177.     def view_folder(self, *args):
  1178.         rows = self.current_view().selectionModel().selectedRows()
  1179.         if not rows or len(rows) == 0:
  1180.             d = error_dialog(self, _('Cannot open folder'), _('No book selected'))
  1181.             d.exec_()
  1182.             return None
  1183.         if not self._view_check(len(rows)):
  1184.             return None
  1185.         for row in rows:
  1186.             path = self.library_view.model().db.abspath(row.row())
  1187.             open_local_file(path)
  1188.         
  1189.  
  1190.     
  1191.     def view_folder_for_id(self, id_):
  1192.         path = self.library_view.model().db.abspath(id_, index_is_id = True)
  1193.         open_local_file(path)
  1194.  
  1195.     
  1196.     def view_book(self, triggered):
  1197.         rows = self.current_view().selectionModel().selectedRows()
  1198.         self._view_books(rows)
  1199.  
  1200.     
  1201.     def view_specific_book(self, index):
  1202.         self._view_books([
  1203.             index])
  1204.  
  1205.     
  1206.     def _view_books(self, rows):
  1207.         if not rows or len(rows) == 0:
  1208.             self._launch_viewer()
  1209.             return None
  1210.         if not self._view_check(len(rows)):
  1211.             return None
  1212.         if self.current_view() is self.library_view:
  1213.             for row in rows:
  1214.                 formats = self.library_view.model().db.formats(row)
  1215.                 title = self.library_view.model().db.title(row)
  1216.                 if not formats:
  1217.                     error_dialog(self, _('Cannot view'), _('%s has no available formats.') % (title,), show = True)
  1218.                     continue
  1219.                 
  1220.                 formats = formats.upper().split(',')
  1221.                 in_prefs = False
  1222.                 for format in prefs['input_format_order']:
  1223.                     if format in formats:
  1224.                         in_prefs = True
  1225.                         self.view_format(row, format)
  1226.                         break
  1227.                         continue
  1228.                 
  1229.                 if not in_prefs:
  1230.                     self.view_format(row, formats[0])
  1231.                     continue
  1232.             
  1233.         else:
  1234.             paths = self.current_view().model().paths(rows)
  1235.             for path in paths:
  1236.                 pt = PersistentTemporaryFile('_viewer_' + os.path.splitext(path)[1])
  1237.                 self.persistent_files.append(pt)
  1238.                 pt.close()
  1239.                 self.device_manager.view_book(Dispatcher(self.book_downloaded_for_viewing), path, pt.name)
  1240.             
  1241.  
  1242.  
  1243.