mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-03 19:17:02 -05:00 
			
		
		
		
	Merge from trunk
This commit is contained in:
		
						commit
						c9cb3eba48
					
				@ -17,6 +17,7 @@ from calibre.db.tests.base import BaseTest
 | 
			
		||||
 | 
			
		||||
class WritingTest(BaseTest):
 | 
			
		||||
 | 
			
		||||
    # Utils {{{
 | 
			
		||||
    def create_getter(self, name, getter=None):
 | 
			
		||||
        if getter is None:
 | 
			
		||||
            if name.endswith('_index'):
 | 
			
		||||
@ -71,6 +72,7 @@ class WritingTest(BaseTest):
 | 
			
		||||
                        'Failed setting for %s, sqlite value not the same: %r != %r'%(
 | 
			
		||||
                            test.name, old_sqlite_res, sqlite_res))
 | 
			
		||||
                del db
 | 
			
		||||
    # }}}
 | 
			
		||||
 | 
			
		||||
    def test_one_one(self):  # {{{
 | 
			
		||||
        'Test setting of values in one-one fields'
 | 
			
		||||
 | 
			
		||||
@ -276,7 +276,6 @@ class Worker(Thread): # Get details {{{
 | 
			
		||||
            self.log.exception('Error parsing authors for url: %r'%self.url)
 | 
			
		||||
            authors = []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if not title or not authors or not asin:
 | 
			
		||||
            self.log.error('Could not find title/authors/asin for %r'%self.url)
 | 
			
		||||
            self.log.error('ASIN: %r Title: %r Authors: %r'%(asin, title,
 | 
			
		||||
@ -431,7 +430,6 @@ class Worker(Thread): # Get details {{{
 | 
			
		||||
        desc = re.sub(r'(?s)<!--.*?-->', '', desc)
 | 
			
		||||
        return sanitize_comments_html(desc)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def parse_comments(self, root):
 | 
			
		||||
        ans = ''
 | 
			
		||||
        desc = root.xpath('//div[@id="ps-content"]/div[@class="content"]')
 | 
			
		||||
@ -637,7 +635,6 @@ class Amazon(Source):
 | 
			
		||||
            mi.tags = list(map(fixcase, mi.tags))
 | 
			
		||||
        mi.isbn = check_isbn(mi.isbn)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def create_query(self, log, title=None, authors=None, identifiers={},  # {{{
 | 
			
		||||
            domain=None):
 | 
			
		||||
        if domain is None:
 | 
			
		||||
@ -724,7 +721,10 @@ class Amazon(Source):
 | 
			
		||||
 | 
			
		||||
        def title_ok(title):
 | 
			
		||||
            title = title.lower()
 | 
			
		||||
            for x in ('bulk pack', '[audiobook]', '[audio cd]'):
 | 
			
		||||
            bad = ['bulk pack', '[audiobook]', '[audio cd]']
 | 
			
		||||
            if self.domain == 'com':
 | 
			
		||||
                bad.append('(spanish edition)')
 | 
			
		||||
            for x in bad:
 | 
			
		||||
                if x in title:
 | 
			
		||||
                    return False
 | 
			
		||||
            return True
 | 
			
		||||
@ -751,7 +751,6 @@ class Amazon(Source):
 | 
			
		||||
                        matches.append(a.get('href'))
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        # Keep only the top 5 matches as the matches are sorted by relevance by
 | 
			
		||||
        # Amazon so lower matches are not likely to be very relevant
 | 
			
		||||
        return matches[:5]
 | 
			
		||||
@ -795,7 +794,6 @@ class Amazon(Source):
 | 
			
		||||
                log.exception(msg)
 | 
			
		||||
            return as_unicode(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        raw = clean_ascii_chars(xml_to_unicode(raw,
 | 
			
		||||
            strip_encoding_pats=True, resolve_entities=True)[0])
 | 
			
		||||
 | 
			
		||||
@ -825,7 +823,6 @@ class Amazon(Source):
 | 
			
		||||
                    # The error is almost always a not found error
 | 
			
		||||
                    found = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if found:
 | 
			
		||||
            matches = self.parse_results_page(root)
 | 
			
		||||
 | 
			
		||||
@ -907,6 +904,11 @@ if __name__ == '__main__': # tests {{{
 | 
			
		||||
            isbn_test, title_test, authors_test, comments_test, series_test)
 | 
			
		||||
    com_tests = [  # {{{
 | 
			
		||||
 | 
			
		||||
            (  # Has a spanish edition
 | 
			
		||||
             {'title':'11/22/63'},
 | 
			
		||||
             [title_test('11/22/63: A Novel', exact=True), authors_test(['Stephen King']),]
 | 
			
		||||
             ),
 | 
			
		||||
 | 
			
		||||
            (  # + in title and uses id="main-image" for cover
 | 
			
		||||
             {'title':'C++ Concurrency in Action'},
 | 
			
		||||
             [title_test('C++ Concurrency in Action: Practical Multithreading',
 | 
			
		||||
@ -917,8 +919,8 @@ if __name__ == '__main__': # tests {{{
 | 
			
		||||
            (  # Series
 | 
			
		||||
                {'identifiers':{'amazon':'0756407117'}},
 | 
			
		||||
                [title_test(
 | 
			
		||||
                "Throne of the Crescent Moon"
 | 
			
		||||
                , exact=True), series_test('Crescent Moon Kingdoms', 1),
 | 
			
		||||
                "Throne of the Crescent Moon",
 | 
			
		||||
                exact=True), series_test('Crescent Moon Kingdoms', 1),
 | 
			
		||||
                comments_test('Makhslood'),
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
@ -926,8 +928,8 @@ if __name__ == '__main__': # tests {{{
 | 
			
		||||
            (  # Different comments markup, using Book Description section
 | 
			
		||||
                {'identifiers':{'amazon':'0982514506'}},
 | 
			
		||||
                [title_test(
 | 
			
		||||
                "Griffin's Destiny: Book Three: The Griffin's Daughter Trilogy"
 | 
			
		||||
                , exact=True),
 | 
			
		||||
                "Griffin's Destiny: Book Three: The Griffin's Daughter Trilogy",
 | 
			
		||||
                exact=True),
 | 
			
		||||
                comments_test('Jelena'), comments_test('Leslie'),
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
@ -279,7 +279,7 @@ class EditMetadataAction(InterfaceAction):
 | 
			
		||||
        '''
 | 
			
		||||
        Edit metadata of selected books in library in bulk.
 | 
			
		||||
        '''
 | 
			
		||||
        rows = [r.row() for r in \
 | 
			
		||||
        rows = [r.row() for r in
 | 
			
		||||
                self.gui.library_view.selectionModel().selectedRows()]
 | 
			
		||||
        m = self.gui.library_view.model()
 | 
			
		||||
        ids = [m.id(r) for r in rows]
 | 
			
		||||
@ -470,38 +470,32 @@ class EditMetadataAction(InterfaceAction):
 | 
			
		||||
            db.set_cover(dest_id, dest_cover)
 | 
			
		||||
 | 
			
		||||
        for key in db.field_metadata:  # loop thru all defined fields
 | 
			
		||||
          if db.field_metadata[key]['is_custom']:
 | 
			
		||||
            colnum = db.field_metadata[key]['colnum']
 | 
			
		||||
            fm = db.field_metadata[key]
 | 
			
		||||
            if not fm['is_custom']:
 | 
			
		||||
                continue
 | 
			
		||||
            dt = fm['datatype']
 | 
			
		||||
            colnum = fm['colnum']
 | 
			
		||||
            # Get orig_dest_comments before it gets changed
 | 
			
		||||
            if db.field_metadata[key]['datatype'] == 'comments':
 | 
			
		||||
            if dt == 'comments':
 | 
			
		||||
                orig_dest_value = db.get_custom(dest_id, num=colnum, index_is_id=True)
 | 
			
		||||
 | 
			
		||||
            for src_id in src_ids:
 | 
			
		||||
                dest_value = db.get_custom(dest_id, num=colnum, index_is_id=True)
 | 
			
		||||
                src_value = db.get_custom(src_id, num=colnum, index_is_id=True)
 | 
			
		||||
              if db.field_metadata[key]['datatype'] == 'comments':
 | 
			
		||||
                if src_value and src_value != orig_dest_value:
 | 
			
		||||
                if (dt == 'comments' and src_value and src_value != orig_dest_value):
 | 
			
		||||
                    if not dest_value:
 | 
			
		||||
                        db.set_custom(dest_id, src_value, num=colnum)
 | 
			
		||||
                    else:
 | 
			
		||||
                        dest_value = unicode(dest_value) + u'\n\n' + unicode(src_value)
 | 
			
		||||
                        db.set_custom(dest_id, dest_value, num=colnum)
 | 
			
		||||
              if db.field_metadata[key]['datatype'] in \
 | 
			
		||||
                ('bool', 'int', 'float', 'rating', 'datetime') \
 | 
			
		||||
                and dest_value is None:
 | 
			
		||||
                if (dt in {'bool', 'int', 'float', 'rating', 'datetime'} and dest_value is None):
 | 
			
		||||
                    db.set_custom(dest_id, src_value, num=colnum)
 | 
			
		||||
              if db.field_metadata[key]['datatype'] == 'series' \
 | 
			
		||||
                and not dest_value:
 | 
			
		||||
                if src_value:
 | 
			
		||||
                if (dt == 'series' and not dest_value and src_value):
 | 
			
		||||
                    src_index = db.get_custom_extra(src_id, num=colnum, index_is_id=True)
 | 
			
		||||
                    db.set_custom(dest_id, src_value, num=colnum, extra=src_index)
 | 
			
		||||
              if (db.field_metadata[key]['datatype'] == 'enumeration' or
 | 
			
		||||
                        (db.field_metadata[key]['datatype'] == 'text' and
 | 
			
		||||
                         not db.field_metadata[key]['is_multiple'])
 | 
			
		||||
                    and not dest_value):
 | 
			
		||||
                if (dt == 'enumeration' or (dt == 'text' and not fm['is_multiple']) and not dest_value):
 | 
			
		||||
                    db.set_custom(dest_id, src_value, num=colnum)
 | 
			
		||||
              if db.field_metadata[key]['datatype'] == 'text' \
 | 
			
		||||
                and db.field_metadata[key]['is_multiple']:
 | 
			
		||||
                if src_value:
 | 
			
		||||
                if (dt == 'text' and fm['is_multiple'] and src_value):
 | 
			
		||||
                    if not dest_value:
 | 
			
		||||
                        dest_value = src_value
 | 
			
		||||
                    else:
 | 
			
		||||
@ -585,7 +579,6 @@ class EditMetadataAction(InterfaceAction):
 | 
			
		||||
            self.apply_pd.value += 1
 | 
			
		||||
        QTimer.singleShot(50, self.do_one_apply)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def apply_mi(self, book_id, mi):
 | 
			
		||||
        db = self.gui.current_db
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,8 @@ from calibre.gui2.dialogs.message_box import ViewLog
 | 
			
		||||
 | 
			
		||||
Question = namedtuple('Question', 'payload callback cancel_callback '
 | 
			
		||||
        'title msg html_log log_viewer_title log_is_file det_msg '
 | 
			
		||||
        'show_copy_button checkbox_msg checkbox_checked')
 | 
			
		||||
        'show_copy_button checkbox_msg checkbox_checked action_callback '
 | 
			
		||||
        'action_label action_icon')
 | 
			
		||||
 | 
			
		||||
class ProceedQuestion(QDialog):
 | 
			
		||||
 | 
			
		||||
@ -51,6 +52,8 @@ class ProceedQuestion(QDialog):
 | 
			
		||||
        self.copy_button = self.bb.addButton(_('&Copy to clipboard'),
 | 
			
		||||
                self.bb.ActionRole)
 | 
			
		||||
        self.copy_button.clicked.connect(self.copy_to_clipboard)
 | 
			
		||||
        self.action_button = self.bb.addButton('', self.bb.ActionRole)
 | 
			
		||||
        self.action_button.clicked.connect(self.action_clicked)
 | 
			
		||||
        self.show_det_msg = _('Show &details')
 | 
			
		||||
        self.hide_det_msg = _('Hide &details')
 | 
			
		||||
        self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole)
 | 
			
		||||
@ -81,6 +84,12 @@ class ProceedQuestion(QDialog):
 | 
			
		||||
                    unicode(self.det_msg.toPlainText())))
 | 
			
		||||
        self.copy_button.setText(_('Copied'))
 | 
			
		||||
 | 
			
		||||
    def action_clicked(self):
 | 
			
		||||
        if self.questions:
 | 
			
		||||
            q = self.questions[0]
 | 
			
		||||
            self.questions[0] = q._replace(callback=q.action_callback)
 | 
			
		||||
        self.accept()
 | 
			
		||||
 | 
			
		||||
    def accept(self):
 | 
			
		||||
        if self.questions:
 | 
			
		||||
            payload, callback, cancel_callback = self.questions[0][:3]
 | 
			
		||||
@ -131,6 +140,11 @@ class ProceedQuestion(QDialog):
 | 
			
		||||
            self.setWindowTitle(question.title)
 | 
			
		||||
            self.log_button.setVisible(bool(question.html_log))
 | 
			
		||||
            self.copy_button.setVisible(bool(question.show_copy_button))
 | 
			
		||||
            self.action_button.setVisible(question.action_callback is not None)
 | 
			
		||||
            if question.action_callback is not None:
 | 
			
		||||
                self.action_button.setText(question.action_label or '')
 | 
			
		||||
                self.action_button.setIcon(
 | 
			
		||||
                    QIcon() if question.action_icon is None else question.action_icon)
 | 
			
		||||
            self.det_msg.setPlainText(question.det_msg or '')
 | 
			
		||||
            self.det_msg.setVisible(False)
 | 
			
		||||
            self.det_msg_toggle.setVisible(bool(question.det_msg))
 | 
			
		||||
@ -146,7 +160,8 @@ class ProceedQuestion(QDialog):
 | 
			
		||||
 | 
			
		||||
    def __call__(self, callback, payload, html_log, log_viewer_title, title,
 | 
			
		||||
            msg, det_msg='', show_copy_button=False, cancel_callback=None,
 | 
			
		||||
            log_is_file=False, checkbox_msg=None, checkbox_checked=False):
 | 
			
		||||
            log_is_file=False, checkbox_msg=None, checkbox_checked=False,
 | 
			
		||||
            action_callback=None, action_label=None, action_icon=None):
 | 
			
		||||
        '''
 | 
			
		||||
        A non modal popup that notifies the user that a background task has
 | 
			
		||||
        been completed. This class guarantees that only a single popup is
 | 
			
		||||
@ -171,11 +186,19 @@ class ProceedQuestion(QDialog):
 | 
			
		||||
                             called with both the payload and the state of the
 | 
			
		||||
                             checkbox as arguments.
 | 
			
		||||
        :param checkbox_checked: If True the checkbox is checked by default.
 | 
			
		||||
        :param action_callback: If not None, an extra button is added, which
 | 
			
		||||
                                when clicked will cause action_callback to be called
 | 
			
		||||
                                instead of callback. action_callback is called in
 | 
			
		||||
                                exactly the same way as callback.
 | 
			
		||||
        :param action_label: The text on the action button
 | 
			
		||||
        :param action_icon: The icon for the action button, must be a QIcon object or None
 | 
			
		||||
 | 
			
		||||
        '''
 | 
			
		||||
        question = Question(payload, callback, cancel_callback, title, msg,
 | 
			
		||||
                html_log, log_viewer_title, log_is_file, det_msg,
 | 
			
		||||
                show_copy_button, checkbox_msg, checkbox_checked)
 | 
			
		||||
        question = Question(
 | 
			
		||||
            payload, callback, cancel_callback, title, msg, html_log,
 | 
			
		||||
            log_viewer_title, log_is_file, det_msg, show_copy_button,
 | 
			
		||||
            checkbox_msg, checkbox_checked, action_callback, action_label,
 | 
			
		||||
            action_icon)
 | 
			
		||||
        self.questions.append(question)
 | 
			
		||||
        self.show_question()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user