class ConfigWidget(QWidget): def __init__(self): QWidget.__init__(self) self.layout = QGridLayout() self.setLayout(self.layout) labelColumnWidths = [] self.opdsUrlLabel = QLabel('OPDS URL: ') self.layout.addWidget(self.opdsUrlLabel, 0, 0) labelColumnWidths.append( self.layout.itemAtPosition(0, 0).sizeHint().width()) print(type(prefs.defaults['opds_url'])) print(type(prefs['opds_url'])) convertSingleStringOpdsUrlPreferenceToListOfStringsPreference() self.opdsUrlEditor = QComboBox(self) self.opdsUrlEditor.addItems(prefs['opds_url']) self.opdsUrlEditor.setEditable(True) self.opdsUrlEditor.setInsertPolicy(QComboBox.InsertAtTop) self.layout.addWidget(self.opdsUrlEditor, 0, 1) self.opdsUrlLabel.setBuddy(self.opdsUrlEditor) self.hideNewsCheckbox = QCheckBox('Hide Newspapers', self) self.hideNewsCheckbox.setChecked(prefs['hideNewspapers']) self.layout.addWidget(self.hideNewsCheckbox, 1, 0) labelColumnWidths.append( self.layout.itemAtPosition(1, 0).sizeHint().width()) self.hideBooksAlreadyInLibraryCheckbox = QCheckBox( 'Hide books already in library', self) self.hideBooksAlreadyInLibraryCheckbox.setChecked( prefs['hideBooksAlreadyInLibrary']) self.layout.addWidget(self.hideBooksAlreadyInLibraryCheckbox, 2, 0) labelColumnWidths.append( self.layout.itemAtPosition(2, 0).sizeHint().width()) labelColumnWidth = max(labelColumnWidths) self.layout.setColumnMinimumWidth(1, labelColumnWidth * 2) def save_settings(self): prefs['hideNewspapers'] = self.hideNewsCheckbox.isChecked() prefs[ 'hideBooksAlreadyInLibrary'] = self.hideBooksAlreadyInLibraryCheckbox.isChecked( ) prefs['opds_url'] = saveOpdsUrlCombobox(self.opdsUrlEditor)
class ConfigWidget(QWidget): def __init__(self): QWidget.__init__(self) self.layout = QGridLayout() self.setLayout(self.layout) labelColumnWidths = [] self.opdsUrlLabel = QLabel('OPDS URL: ') self.layout.addWidget(self.opdsUrlLabel, 0, 0) labelColumnWidths.append(self.layout.itemAtPosition(0, 0).sizeHint().width()) print type(prefs.defaults['opds_url']) print type(prefs['opds_url']) convertSingleStringOpdsUrlPreferenceToListOfStringsPreference() self.opdsUrlEditor = QComboBox(self) self.opdsUrlEditor.addItems(prefs['opds_url']) self.opdsUrlEditor.setEditable(True) self.opdsUrlEditor.setInsertPolicy(QComboBox.InsertAtTop) self.layout.addWidget(self.opdsUrlEditor, 0, 1) self.opdsUrlLabel.setBuddy(self.opdsUrlEditor) self.hideNewsCheckbox = QCheckBox('Hide Newspapers', self) self.hideNewsCheckbox.setChecked(prefs['hideNewspapers']) self.layout.addWidget(self.hideNewsCheckbox, 1, 0) labelColumnWidths.append(self.layout.itemAtPosition(1, 0).sizeHint().width()) self.hideBooksAlreadyInLibraryCheckbox = QCheckBox('Hide books already in library', self) self.hideBooksAlreadyInLibraryCheckbox.setChecked(prefs['hideBooksAlreadyInLibrary']) self.layout.addWidget(self.hideBooksAlreadyInLibraryCheckbox, 2, 0) labelColumnWidths.append(self.layout.itemAtPosition(2, 0).sizeHint().width()) labelColumnWidth = max(labelColumnWidths) self.layout.setColumnMinimumWidth(1, labelColumnWidth * 2) def save_settings(self): prefs['hideNewspapers'] = self.hideNewsCheckbox.isChecked() prefs['hideBooksAlreadyInLibrary'] = self.hideBooksAlreadyInLibraryCheckbox.isChecked() prefs['opds_url'] = saveOpdsUrlCombobox(self.opdsUrlEditor)
class SpotlightLibreDialog(QDialog): def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config # The current database shown in the GUI > class LibraryDatabase2 self.db = gui.current_db self.l = QVBoxLayout() self.setLayout(self.l) # Label self.labelText = QLabel('Use "and" and "or" for the search.') self.l.addWidget(self.labelText) # Title self.setWindowTitle('SpotlightLibre Full Text Search') self.setWindowIcon(icon) # Search window self.searchTextWindow = QComboBox() self.searchTextWindow.setEditable(True) self.l.addWidget(self.searchTextWindow) self.searchTextWindow.setFocus() self.searchTextWindow.setInsertPolicy(QComboBox.NoInsert) self.searchTextWindow.setDuplicatesEnabled(False) #Completer for the seach window self.completer = QCompleter() self.completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion ) self.searchTextWindow.setCompleter(self.completer) # output window self.outputWindow = QLabel() self.l.addWidget(self.outputWindow) # search button 1 self.doSearchButton = QPushButton('Search and replace the filter', self) self.doSearchButton.clicked.connect(self.spotlightSearchNew) self.l.addWidget(self.doSearchButton) self.doSearchButton.setDefault(True) # search button 2 self.doSearchButton = QPushButton('Search and add to filter', self) self.doSearchButton.clicked.connect(self.spotlightSearchAdd) self.l.addWidget(self.doSearchButton) # about button self.aboutButton = QPushButton('About', self) self.aboutButton.clicked.connect(self.about) self.l.addWidget(self.aboutButton) self.resize(self.sizeHint()) #self.resize(500, self.height()) def about(self): # Get the about text from a file inside the plugin zip file # The get_resources function is a builtin function defined for all your # plugin code. It loads files from the plugin zip file. It returns # the bytes from the specified file. text = get_resources('about.txt') #box = QMessageBox() #box.about(self, 'About the SpotlightLibre Full Text Search \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t',text.decode('utf-8')) #self.resize(600, self.height()) self.box = AboutWindow() self.box.setWindowTitle("About the SpotlightLibre Full Text Search Plugin") self.box.textWindow.setText(text) self.box.textWindow.setReadOnly(True) self.box.resize(600, 500) self.box.show() def spotlightSearchNew(self): self.searchAdd = False self.spotlightSearch() def spotlightSearchAdd(self): self.searchAdd = True self.spotlightSearch() def spotlightSearch(self): self.searchText = str(self.searchTextWindow.currentText())# search text from the plugin gui self.searchTextWindow.insertItem(0, self.searchText) # self.cmd = 'mdfind -onlyin '+ prefs['pathToLibrary']+' ' # saved to allow for checkbox option later? print(self.db.library_path) self.cmd = 'mdfind -onlyin "'+self.db.library_path+'" ' self.cmdString = self.cmd + '"'+self.searchText+'"' print(self.cmdString) self.p = Popen(self.cmdString, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) self.output = self.p.stdout.read()# output from the spotlight search print(self.output) self.found = '' self.found = re.findall(r" \((\d+)\)\/", self.output)# regex to find the calibre ids in the folder names self.wholeString = '' if len(self.found) == 0 : self.outputWindow.setText('no books found' + ' for ' + self.searchText) else : self.wholeString = '#cid:' for elem in self.found: self.wholeString += '=' + elem + ' or ' self.wholeString = self.wholeString[:-4] self.outputWindow.setText(str(len(self.found)) + ' books found' + ' for ' + self.searchText) if self.searchAdd == True : self.oldFilter = self.gui.search.text() self.wholeString = self.oldFilter + ' and (' + self.wholeString + ')' self.searchTextWindow.clearEditText() self.gui.search.setEditText(self.wholeString) # set calibre search to the string found by spotlight self.gui.search.do_search() def config(self): self.do_user_config(parent=self)
class OpdsDialog(QDialog): def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config self.db = gui.current_db.new_api # The model for the book list self.model = OpdsBooksModel(None, self.dummy_books(), self.db) self.searchproxymodel = QSortFilterProxyModel(self) self.searchproxymodel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.searchproxymodel.setFilterKeyColumn(-1) self.searchproxymodel.setSourceModel(self.model) self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowTitle("OPDS Client") self.setWindowIcon(icon) labelColumnWidths = [] self.opdsUrlLabel = QLabel("OPDS URL: ") self.layout.addWidget(self.opdsUrlLabel, 0, 0) labelColumnWidths.append(self.layout.itemAtPosition(0, 0).sizeHint().width()) config.convertSingleStringOpdsUrlPreferenceToListOfStringsPreference() self.opdsUrlEditor = QComboBox(self) self.opdsUrlEditor.activated.connect(self.opdsUrlEditorActivated) self.opdsUrlEditor.addItems(prefs["opds_url"]) self.opdsUrlEditor.setEditable(True) self.opdsUrlEditor.setInsertPolicy(QComboBox.InsertAtTop) self.layout.addWidget(self.opdsUrlEditor, 0, 1, 1, 3) self.opdsUrlLabel.setBuddy(self.opdsUrlEditor) buttonColumnNumber = 7 buttonColumnWidths = [] self.about_button = QPushButton("About", self) self.about_button.setAutoDefault(False) self.about_button.clicked.connect(self.about) self.layout.addWidget(self.about_button, 0, buttonColumnNumber) buttonColumnWidths.append( self.layout.itemAtPosition(0, buttonColumnNumber).sizeHint().width() ) # Initially download the catalogs found in the root catalog of the URL # selected at startup. Fail quietly on failing to open the URL catalogsTuple = self.model.downloadOpdsRootCatalog( self.gui, self.opdsUrlEditor.currentText(), False ) print(catalogsTuple) firstCatalogTitle = catalogsTuple[0] self.currentOpdsCatalogs = catalogsTuple[1] # A dictionary of title->feedURL self.opdsCatalogSelectorLabel = QLabel("OPDS Catalog:") self.layout.addWidget(self.opdsCatalogSelectorLabel, 1, 0) labelColumnWidths.append(self.layout.itemAtPosition(1, 0).sizeHint().width()) self.opdsCatalogSelector = QComboBox(self) self.opdsCatalogSelector.setEditable(False) self.opdsCatalogSelectorModel = QStringListModel(self.currentOpdsCatalogs.keys()) self.opdsCatalogSelector.setModel(self.opdsCatalogSelectorModel) self.opdsCatalogSelector.setCurrentText(firstCatalogTitle) self.layout.addWidget(self.opdsCatalogSelector, 1, 1, 1, 3) self.download_opds_button = QPushButton("Download OPDS", self) self.download_opds_button.setAutoDefault(False) self.download_opds_button.clicked.connect(self.download_opds) self.layout.addWidget(self.download_opds_button, 1, buttonColumnNumber) buttonColumnWidths.append( self.layout.itemAtPosition(1, buttonColumnNumber).sizeHint().width() ) # Search GUI self.searchEditor = QLineEdit(self) self.searchEditor.returnPressed.connect(self.searchBookList) self.layout.addWidget(self.searchEditor, 2, buttonColumnNumber - 2, 1, 2) self.searchButton = QPushButton("Search", self) self.searchButton.setAutoDefault(False) self.searchButton.clicked.connect(self.searchBookList) self.layout.addWidget(self.searchButton, 2, buttonColumnNumber) buttonColumnWidths.append( self.layout.itemAtPosition(2, buttonColumnNumber).sizeHint().width() ) # The main book list self.library_view = QTableView(self) self.library_view.setAlternatingRowColors(True) self.library_view.setModel(self.searchproxymodel) self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.library_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.resizeAllLibraryViewLinesToHeaderHeight() self.library_view.resizeColumnsToContents() self.layout.addWidget(self.library_view, 3, 0, 3, buttonColumnNumber + 1) self.hideNewsCheckbox = QCheckBox("Hide Newspapers", self) self.hideNewsCheckbox.clicked.connect(self.setHideNewspapers) self.hideNewsCheckbox.setChecked(prefs["hideNewspapers"]) self.layout.addWidget(self.hideNewsCheckbox, 6, 0, 1, 3) self.hideBooksAlreadyInLibraryCheckbox = QCheckBox("Hide books already in library", self) self.hideBooksAlreadyInLibraryCheckbox.clicked.connect(self.setHideBooksAlreadyInLibrary) self.hideBooksAlreadyInLibraryCheckbox.setChecked(prefs["hideBooksAlreadyInLibrary"]) self.layout.addWidget(self.hideBooksAlreadyInLibraryCheckbox, 7, 0, 1, 3) # Let the checkbox initial state control the filtering self.model.setFilterBooksThatAreNewspapers(self.hideNewsCheckbox.isChecked()) self.model.setFilterBooksThatAreAlreadyInLibrary( self.hideBooksAlreadyInLibraryCheckbox.isChecked() ) self.downloadButton = QPushButton("Download selected books", self) self.downloadButton.setAutoDefault(False) self.downloadButton.clicked.connect(self.downloadSelectedBooks) self.layout.addWidget(self.downloadButton, 6, buttonColumnNumber) buttonColumnWidths.append( self.layout.itemAtPosition(6, buttonColumnNumber).sizeHint().width() ) self.fixTimestampButton = QPushButton("Fix timestamps of selection", self) self.fixTimestampButton.setAutoDefault(False) self.fixTimestampButton.clicked.connect(self.fixBookTimestamps) self.layout.addWidget(self.fixTimestampButton, 7, buttonColumnNumber) buttonColumnWidths.append( self.layout.itemAtPosition(7, buttonColumnNumber).sizeHint().width() ) # Make all columns of the grid layout the same width as the button column buttonColumnWidth = max(buttonColumnWidths) for columnNumber in range(0, buttonColumnNumber): self.layout.setColumnMinimumWidth(columnNumber, buttonColumnWidth) # Make sure the first column isn't wider than the labels it holds labelColumnWidth = max(labelColumnWidths) self.layout.setColumnMinimumWidth(0, labelColumnWidth) self.resize(self.sizeHint()) def opdsUrlEditorActivated(self, text): prefs["opds_url"] = config.saveOpdsUrlCombobox(self.opdsUrlEditor) catalogsTuple = self.model.downloadOpdsRootCatalog( self.gui, self.opdsUrlEditor.currentText(), True ) firstCatalogTitle = catalogsTuple[0] self.currentOpdsCatalogs = catalogsTuple[1] # A dictionary of title->feedURL self.opdsCatalogSelectorModel.setStringList(self.currentOpdsCatalogs.keys()) self.opdsCatalogSelector.setCurrentText(firstCatalogTitle) def setHideNewspapers(self, checked): prefs["hideNewspapers"] = checked self.model.setFilterBooksThatAreNewspapers(checked) self.resizeAllLibraryViewLinesToHeaderHeight() def setHideBooksAlreadyInLibrary(self, checked): prefs["hideBooksAlreadyInLibrary"] = checked self.model.setFilterBooksThatAreAlreadyInLibrary(checked) self.resizeAllLibraryViewLinesToHeaderHeight() def searchBookList(self): searchString = self.searchEditor.text() print("starting book list search for: %s" % searchString) self.searchproxymodel.setFilterFixedString(searchString) def about(self): text = get_resources("about.txt") QMessageBox.about(self, "About the OPDS Client plugin", text.decode("utf-8")) def download_opds(self): opdsCatalogUrl = self.currentOpdsCatalogs.get(self.opdsCatalogSelector.currentText(), None) if opdsCatalogUrl is None: # Just give up quietly return self.model.downloadOpdsCatalog(self.gui, opdsCatalogUrl) if self.model.isCalibreOpdsServer(): self.model.downloadMetadataUsingCalibreRestApi(self.opdsUrlEditor.currentText()) self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.resizeAllLibraryViewLinesToHeaderHeight() self.resize(self.sizeHint()) def config(self): self.do_user_config(parent=self) def downloadSelectedBooks(self): selectionmodel = self.library_view.selectionModel() if selectionmodel.hasSelection(): rows = selectionmodel.selectedRows() for row in reversed(rows): book = row.data(Qt.UserRole) self.downloadBook(book) def downloadBook(self, book): if len(book.links) > 0: self.gui.download_ebook(book.links[0]) def fixBookTimestamps(self): selectionmodel = self.library_view.selectionModel() if selectionmodel.hasSelection(): rows = selectionmodel.selectedRows() for row in reversed(rows): book = row.data(Qt.UserRole) self.fixBookTimestamp(book) def fixBookTimestamp(self, book): bookTimestamp = book.timestamp identicalBookIds = self.findIdenticalBooksForBooksWithMultipleAuthors(book) bookIdToValMap = {} for identicalBookId in identicalBookIds: bookIdToValMap[identicalBookId] = bookTimestamp if len(bookIdToValMap) < 1: print("Failed to set timestamp of book: %s" % book) self.db.set_field("timestamp", bookIdToValMap) def findIdenticalBooksForBooksWithMultipleAuthors(self, book): authorsList = book.authors if len(authorsList) < 2: return self.db.find_identical_books(book) # Try matching the authors one by one identicalBookIds = set() for author in authorsList: singleAuthorBook = Metadata(book.title, [author]) singleAuthorIdenticalBookIds = self.db.find_identical_books(singleAuthorBook) identicalBookIds = identicalBookIds.union(singleAuthorIdenticalBookIds) return identicalBookIds def dummy_books(self): dummy_author = " " * 40 dummy_title = " " * 60 books_list = [] for line in range(1, 10): book = DynamicBook() book.author = dummy_author book.title = dummy_title book.updated = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00") book.id = "" books_list.append(book) return books_list def resizeAllLibraryViewLinesToHeaderHeight(self): rowHeight = self.library_view.horizontalHeader().height() for rowNumber in range(0, self.library_view.model().rowCount()): self.library_view.setRowHeight(rowNumber, rowHeight)
class RecollFulltextSearchDialog(QDialog): def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config # The current database shown in the GUI # db is an instance of the class LibraryDatabase2 from database.py # This class has many, many methods that allow you to do a lot of # things. self.db = gui.current_db self.l = QVBoxLayout() self.setLayout(self.l) # Label self.labelText = QLabel('Use "and" and "or" for the search.') self.l.addWidget(self.labelText) # Title self.setWindowTitle('Recoll Full Text Search') self.setWindowIcon(icon) # Search window self.searchTextWindow = QComboBox() self.searchTextWindow.setEditable(True) self.l.addWidget(self.searchTextWindow) self.searchTextWindow.setFocus() self.searchTextWindow.setInsertPolicy(QComboBox.NoInsert) self.searchTextWindow.setDuplicatesEnabled(False) #Completer for the seach window self.completer = QCompleter() self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.searchTextWindow.setCompleter(self.completer) # output window self.outputWindow = QLabel() self.l.addWidget(self.outputWindow) # search button 1 self.doSearchButton = QPushButton('Search and replace the filter', self) self.doSearchButton.clicked.connect(self.recollSearchNew) self.l.addWidget(self.doSearchButton) self.doSearchButton.setDefault(True) # search button 2 self.doSearchButton = QPushButton('Search and add to filter', self) self.doSearchButton.clicked.connect(self.recollSearchAdd) self.l.addWidget(self.doSearchButton) # update database button 1 self.updateDatabaseButton = QPushButton('Update recoll database', self) self.updateDatabaseButton.clicked.connect(self.updateDatabase) self.l.addWidget(self.updateDatabaseButton) # update database button 2 self.newDatabaseButton = QPushButton('Make new recoll database', self) self.newDatabaseButton.clicked.connect(self.newDatabase) self.l.addWidget(self.newDatabaseButton) # config button self.configButton = QPushButton('Configure this plugin', self) self.configButton.clicked.connect(self.config) self.l.addWidget(self.configButton) # about button self.aboutButton = QPushButton('About', self) self.aboutButton.clicked.connect(self.about) self.l.addWidget(self.aboutButton) self.resize(self.sizeHint()) #self.resize(500, self.height()) def about(self): # Get the about text from a file inside the plugin zip file # The get_resources function is a builtin function defined for all your # plugin code. It loads files from the plugin zip file. It returns # the bytes from the specified file. text = get_resources('about.txt') #box = QMessageBox() #box.about(self, 'About the Recoll Full Text Search \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t',text.decode('utf-8')) #self.resize(600, self.height()) self.box = AboutWindow() self.box.setWindowTitle("About the Recoll Full Text Search Plugin") self.box.textWindow.setText(text) self.box.textWindow.setReadOnly(True) self.box.resize(600, 500) self.box.show() def updateDatabase(self): self.replaceDatabase = False self.makeDatabase() def newDatabase(self): self.replaceDatabase = True self.makeDatabase() def recollSearchNew(self): self.searchAdd = False self.recollSearch() def recollSearchAdd(self): self.searchAdd = True self.recollSearch() def makeDatabase(self): '''Runs recollindex outside calibre like in a terminal. Look for recollindex for more information about the flags and options''' self.cmd = [ prefs['pathToRecoll'] + '/recollindex', '-c', prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin' ] #TODO: Fix for Linux #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recollindex -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin' if self.replaceDatabase == True: self.cmd += [' -z'] self.p = Popen(self.cmd, shell=False) # TODO: Was close_fds nessesary? check it on linux #self.p = Popen(self.cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) box = QMessageBox() box.about( self, 'Please read! \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t', 'Depending on you library size this operation can take a lot of time.\nThe process runs outside calibre so you can use or close it, but do not use this plugin.\nFor now there is no information about when recoll finishs,\nso look up, whether a recoll of recollindex process is running on you system.' ) def recollSearch(self): '''Runs recoll outside calibre like in a terminal. Look for recollindex for more information about the flags and options''' self.searchText = str(self.searchTextWindow.currentText() ) # search text from the plugin gui self.searchTextWindow.insertItem(0, self.searchText) #TODO: Fix Linux #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recoll -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin -b -t ' self.cmd = [ prefs['pathToRecoll'] + '/recoll', '-c', prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin', '-b', '-t' ] self.cmdString = self.cmd + [self.searchText] # TODO: Was close_fds nessesary? check it on linux #self.p = Popen(self.cmdString, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) self.p = Popen(self.cmdString, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) self.output = self.p.stdout.read() # output from the recoll search self.found = list(set(re.findall( r" \((\d+)\)\/[^/]*", self.output))) # regex to find the calibre ids in the folder names self.wholeString = '' if len(self.found) == 0: self.outputWindow.setText('no books found' + ' for ' + self.searchText) else: for elem in self.found[:400]: self.wholeString += 'id:=' + elem + ' or ' self.wholeString = self.wholeString[:-4] if len(self.found) > 400: self.outputWindow.setText( str(len(self.found)) + ' books found' + ' for ' + self.searchText + '. Only the first 400 books are shown') else: self.outputWindow.setText( str(len(self.found)) + ' books found' + ' for ' + self.searchText) if self.searchAdd == True: self.oldFilter = self.gui.search.text() self.wholeString = self.oldFilter + ' and (' + self.wholeString + ')' self.searchTextWindow.clearEditText() self.gui.search.setEditText( self.wholeString ) # set calibre search to the string found by recoll self.gui.search.do_search() def config(self): self.do_user_config(parent=self)
class RecollFulltextSearchDialog(QDialog): def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config # The current database shown in the GUI # db is an instance of the class LibraryDatabase2 from database.py # This class has many, many methods that allow you to do a lot of # things. self.db = gui.current_db self.l = QVBoxLayout() self.setLayout(self.l) # Label self.labelText = QLabel('Use "and" and "or" for the search.') self.l.addWidget(self.labelText) # Title self.setWindowTitle('Recoll Full Text Search') self.setWindowIcon(icon) # Search window self.searchTextWindow = QComboBox() self.searchTextWindow.setEditable(True) self.l.addWidget(self.searchTextWindow) self.searchTextWindow.setFocus() self.searchTextWindow.setInsertPolicy(QComboBox.NoInsert) self.searchTextWindow.setDuplicatesEnabled(False) #Completer for the seach window self.completer = QCompleter() self.completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion ) self.searchTextWindow.setCompleter(self.completer) # output window self.outputWindow = QLabel() self.l.addWidget(self.outputWindow) # search button 1 self.doSearchButton = QPushButton('Search and replace the filter', self) self.doSearchButton.clicked.connect(self.recollSearchNew) self.l.addWidget(self.doSearchButton) self.doSearchButton.setDefault(True) # search button 2 self.doSearchButton = QPushButton('Search and add to filter', self) self.doSearchButton.clicked.connect(self.recollSearchAdd) self.l.addWidget(self.doSearchButton) # update database button 1 self.updateDatabaseButton = QPushButton('Update recoll database', self) self.updateDatabaseButton.clicked.connect(self.updateDatabase) self.l.addWidget(self.updateDatabaseButton) # update database button 2 self.newDatabaseButton = QPushButton('Make new recoll database', self) self.newDatabaseButton.clicked.connect(self.newDatabase) self.l.addWidget(self.newDatabaseButton) # config button self.configButton = QPushButton('Configure this plugin', self) self.configButton.clicked.connect(self.config) self.l.addWidget(self.configButton) # about button self.aboutButton = QPushButton('About', self) self.aboutButton.clicked.connect(self.about) self.l.addWidget(self.aboutButton) self.resize(self.sizeHint()) #self.resize(500, self.height()) def about(self): # Get the about text from a file inside the plugin zip file # The get_resources function is a builtin function defined for all your # plugin code. It loads files from the plugin zip file. It returns # the bytes from the specified file. text = get_resources('about.txt') #box = QMessageBox() #box.about(self, 'About the Recoll Full Text Search \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t',text.decode('utf-8')) #self.resize(600, self.height()) self.box = AboutWindow() self.box.setWindowTitle("About the Recoll Full Text Search Plugin") self.box.textWindow.setText(text) self.box.textWindow.setReadOnly(True) self.box.resize(600, 500) self.box.show() def updateDatabase(self): self.replaceDatabase =False self.makeDatabase() def newDatabase(self): self.replaceDatabase = True self.makeDatabase() def recollSearchNew(self): self.searchAdd = False self.recollSearch() def recollSearchAdd(self): self.searchAdd = True self.recollSearch() def makeDatabase(self): '''Runs recollindex outside calibre like in a terminal. Look for recollindex for more information about the flags and options''' self.cmd = [prefs['pathToRecoll'] + '/recollindex', '-c', prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin'] #TODO: Fix for Linux #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recollindex -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin' if self.replaceDatabase == True : self.cmd += [' -z'] self.p = Popen(self.cmd, shell=False) # TODO: Was close_fds nessesary? check it on linux #self.p = Popen(self.cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) box = QMessageBox() box.about(self, 'Please read! \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t','Depending on you library size this operation can take a lot of time.\nThe process runs outside calibre so you can use or close it, but do not use this plugin.\nFor now there is no information about when recoll finishs,\nso look up, whether a recoll of recollindex process is running on you system.') def recollSearch(self): '''Runs recoll outside calibre like in a terminal. Look for recollindex for more information about the flags and options''' self.searchText = str(self.searchTextWindow.currentText())# search text from the plugin gui self.searchTextWindow.insertItem(0, self.searchText) #TODO: Fix Linux #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recoll -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin -b -t ' self.cmd = [prefs['pathToRecoll'] + '/recoll', '-c', prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin', '-b', '-t'] self.cmdString = self.cmd + [self.searchText] # TODO: Was close_fds nessesary? check it on linux #self.p = Popen(self.cmdString, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) self.p = Popen(self.cmdString, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) self.output = self.p.stdout.read()# output from the recoll search self.found = list(set(re.findall(r" \((\d+)\)\/[^/]*", self.output)))# regex to find the calibre ids in the folder names self.wholeString = '' if len(self.found) == 0 : self.outputWindow.setText('no books found' + ' for ' + self.searchText) else : for elem in self.found[:400]: self.wholeString += 'id:=' + elem + ' or ' self.wholeString = self.wholeString[:-4] if len(self.found) > 400 : self.outputWindow.setText(str(len(self.found)) + ' books found' + ' for ' + self.searchText+ '. Only the first 400 books are shown') else : self.outputWindow.setText(str(len(self.found)) + ' books found' + ' for ' + self.searchText) if self.searchAdd == True : self.oldFilter = self.gui.search.text() self.wholeString = self.oldFilter + ' and (' + self.wholeString + ')' self.searchTextWindow.clearEditText() self.gui.search.setEditText(self.wholeString) # set calibre search to the string found by recoll self.gui.search.do_search() def config(self): self.do_user_config(parent=self)
class OpdsDialog(QDialog): def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config self.db = gui.current_db.new_api # The model for the book list self.model = OpdsBooksModel(None, self.dummy_books(), self.db) self.searchproxymodel = QSortFilterProxyModel(self) self.searchproxymodel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.searchproxymodel.setFilterKeyColumn(-1) self.searchproxymodel.setSourceModel(self.model) self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowTitle('OPDS Client') self.setWindowIcon(icon) labelColumnWidths = [] self.opdsUrlLabel = QLabel('OPDS URL: ') self.layout.addWidget(self.opdsUrlLabel, 0, 0) labelColumnWidths.append(self.layout.itemAtPosition(0, 0).sizeHint().width()) config.convertSingleStringOpdsUrlPreferenceToListOfStringsPreference() self.opdsUrlEditor = QComboBox(self) self.opdsUrlEditor.activated.connect(self.opdsUrlEditorActivated) self.opdsUrlEditor.addItems(prefs['opds_url']) self.opdsUrlEditor.setEditable(True) self.opdsUrlEditor.setInsertPolicy(QComboBox.InsertAtTop) self.layout.addWidget(self.opdsUrlEditor, 0, 1, 1, 3) self.opdsUrlLabel.setBuddy(self.opdsUrlEditor) buttonColumnNumber = 7 buttonColumnWidths = [] self.about_button = QPushButton('About', self) self.about_button.setAutoDefault(False) self.about_button.clicked.connect(self.about) self.layout.addWidget(self.about_button, 0, buttonColumnNumber) buttonColumnWidths.append(self.layout.itemAtPosition(0, buttonColumnNumber).sizeHint().width()) # Initially download the catalogs found in the root catalog of the URL # selected at startup. Fail quietly on failing to open the URL catalogsTuple = self.model.downloadOpdsRootCatalog(self.gui, self.opdsUrlEditor.currentText(), False) print catalogsTuple firstCatalogTitle = catalogsTuple[0] self.currentOpdsCatalogs = catalogsTuple[1] # A dictionary of title->feedURL self.opdsCatalogSelectorLabel = QLabel('OPDS Catalog:') self.layout.addWidget(self.opdsCatalogSelectorLabel, 1, 0) labelColumnWidths.append(self.layout.itemAtPosition(1, 0).sizeHint().width()) self.opdsCatalogSelector = QComboBox(self) self.opdsCatalogSelector.setEditable(False) self.opdsCatalogSelectorModel = QStringListModel(self.currentOpdsCatalogs.keys()) self.opdsCatalogSelector.setModel(self.opdsCatalogSelectorModel) self.opdsCatalogSelector.setCurrentText(firstCatalogTitle) self.layout.addWidget(self.opdsCatalogSelector, 1, 1, 1, 3) self.download_opds_button = QPushButton('Download OPDS', self) self.download_opds_button.setAutoDefault(False) self.download_opds_button.clicked.connect(self.download_opds) self.layout.addWidget(self.download_opds_button, 1, buttonColumnNumber) buttonColumnWidths.append(self.layout.itemAtPosition(1, buttonColumnNumber).sizeHint().width()) # Search GUI self.searchEditor = QLineEdit(self) self.searchEditor.returnPressed.connect(self.searchBookList) self.layout.addWidget(self.searchEditor, 2, buttonColumnNumber - 2, 1, 2) self.searchButton = QPushButton('Search', self) self.searchButton.setAutoDefault(False) self.searchButton.clicked.connect(self.searchBookList) self.layout.addWidget(self.searchButton, 2, buttonColumnNumber) buttonColumnWidths.append(self.layout.itemAtPosition(2, buttonColumnNumber).sizeHint().width()) # The main book list self.library_view = QTableView(self) self.library_view.setAlternatingRowColors(True) self.library_view.setModel(self.searchproxymodel) self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.library_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.resizeAllLibraryViewLinesToHeaderHeight() self.library_view.resizeColumnsToContents() self.layout.addWidget(self.library_view, 3, 0, 3, buttonColumnNumber + 1) self.hideNewsCheckbox = QCheckBox('Hide Newspapers', self) self.hideNewsCheckbox.clicked.connect(self.setHideNewspapers) self.hideNewsCheckbox.setChecked(prefs['hideNewspapers']) self.layout.addWidget(self.hideNewsCheckbox, 6, 0, 1, 3) self.hideBooksAlreadyInLibraryCheckbox = QCheckBox('Hide books already in library', self) self.hideBooksAlreadyInLibraryCheckbox.clicked.connect(self.setHideBooksAlreadyInLibrary) self.hideBooksAlreadyInLibraryCheckbox.setChecked(prefs['hideBooksAlreadyInLibrary']) self.layout.addWidget(self.hideBooksAlreadyInLibraryCheckbox, 7, 0, 1, 3) # Let the checkbox initial state control the filtering self.model.setFilterBooksThatAreNewspapers(self.hideNewsCheckbox.isChecked()) self.model.setFilterBooksThatAreAlreadyInLibrary(self.hideBooksAlreadyInLibraryCheckbox.isChecked()) self.downloadButton = QPushButton('Download selected books', self) self.downloadButton.setAutoDefault(False) self.downloadButton.clicked.connect(self.downloadSelectedBooks) self.layout.addWidget(self.downloadButton, 6, buttonColumnNumber) buttonColumnWidths.append(self.layout.itemAtPosition(6, buttonColumnNumber).sizeHint().width()) self.fixTimestampButton = QPushButton('Fix timestamps of selection', self) self.fixTimestampButton.setAutoDefault(False) self.fixTimestampButton.clicked.connect(self.fixBookTimestamps) self.layout.addWidget(self.fixTimestampButton, 7, buttonColumnNumber) buttonColumnWidths.append(self.layout.itemAtPosition(7, buttonColumnNumber).sizeHint().width()) # Make all columns of the grid layout the same width as the button column buttonColumnWidth = max(buttonColumnWidths) for columnNumber in range(0, buttonColumnNumber): self.layout.setColumnMinimumWidth(columnNumber, buttonColumnWidth) # Make sure the first column isn't wider than the labels it holds labelColumnWidth = max(labelColumnWidths) self.layout.setColumnMinimumWidth(0, labelColumnWidth) self.resize(self.sizeHint()) def opdsUrlEditorActivated(self, text): prefs['opds_url'] = config.saveOpdsUrlCombobox(self.opdsUrlEditor) catalogsTuple = self.model.downloadOpdsRootCatalog(self.gui, self.opdsUrlEditor.currentText(), True) firstCatalogTitle = catalogsTuple[0] self.currentOpdsCatalogs = catalogsTuple[1] # A dictionary of title->feedURL self.opdsCatalogSelectorModel.setStringList(self.currentOpdsCatalogs.keys()) self.opdsCatalogSelector.setCurrentText(firstCatalogTitle) def setHideNewspapers(self, checked): prefs['hideNewspapers'] = checked self.model.setFilterBooksThatAreNewspapers(checked) self.resizeAllLibraryViewLinesToHeaderHeight() def setHideBooksAlreadyInLibrary(self, checked): prefs['hideBooksAlreadyInLibrary'] = checked self.model.setFilterBooksThatAreAlreadyInLibrary(checked) self.resizeAllLibraryViewLinesToHeaderHeight() def searchBookList(self): searchString = self.searchEditor.text() print "starting book list search for: %s" % searchString self.searchproxymodel.setFilterFixedString(searchString) def about(self): text = get_resources('about.txt') QMessageBox.about(self, 'About the OPDS Client plugin', text.decode('utf-8')) def download_opds(self): opdsCatalogUrl = self.currentOpdsCatalogs.get(self.opdsCatalogSelector.currentText(), None) if opdsCatalogUrl is None: # Just give up quietly return self.model.downloadOpdsCatalog(self.gui, opdsCatalogUrl) if self.model.isCalibreOpdsServer(): self.model.downloadMetadataUsingCalibreRestApi(self.opdsUrlEditor.currentText()) self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.resizeAllLibraryViewLinesToHeaderHeight() self.resize(self.sizeHint()) def config(self): self.do_user_config(parent=self) def downloadSelectedBooks(self): selectionmodel = self.library_view.selectionModel() if selectionmodel.hasSelection(): rows = selectionmodel.selectedRows() for row in reversed(rows): book = row.data(Qt.UserRole) self.downloadBook(book) def downloadBook(self, book): if len(book.links) > 0: self.gui.download_ebook(book.links[0]) def fixBookTimestamps(self): selectionmodel = self.library_view.selectionModel() if selectionmodel.hasSelection(): rows = selectionmodel.selectedRows() for row in reversed(rows): book = row.data(Qt.UserRole) self.fixBookTimestamp(book) def fixBookTimestamp(self, book): bookTimestamp = book.timestamp identicalBookIds = self.findIdenticalBooksForBooksWithMultipleAuthors(book) bookIdToValMap = {} for identicalBookId in identicalBookIds: bookIdToValMap[identicalBookId] = bookTimestamp if len(bookIdToValMap) < 1: print "Failed to set timestamp of book: %s" % book self.db.set_field('timestamp', bookIdToValMap) def findIdenticalBooksForBooksWithMultipleAuthors(self, book): authorsList = book.authors if len(authorsList) < 2: return self.db.find_identical_books(book) # Try matching the authors one by one identicalBookIds = set() for author in authorsList: singleAuthorBook = Metadata(book.title, [author]) singleAuthorIdenticalBookIds = self.db.find_identical_books(singleAuthorBook) identicalBookIds = identicalBookIds.union(singleAuthorIdenticalBookIds) return identicalBookIds def dummy_books(self): dummy_author = ' ' * 40 dummy_title = ' ' * 60 books_list = [] for line in range (1, 10): book = DynamicBook() book.author = dummy_author book.title = dummy_title book.updated = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S+00:00') book.id = '' books_list.append(book) return books_list def resizeAllLibraryViewLinesToHeaderHeight(self): rowHeight = self.library_view.horizontalHeader().height() for rowNumber in range (0, self.library_view.model().rowCount()): self.library_view.setRowHeight(rowNumber, rowHeight)