branch: externals/calibre
commit 5d4952641441ad91e3d260f2aebaaa482c0695a4
Author: Kjartan Óli Ágústsson <[email protected]>
Commit: Kjartan Óli Ágústsson <[email protected]>
Add support for search and virtual libraries
* README: Add mention of search and virtual libraries
* calibre-db.el (calibre-db--get-authors, calibre-db--get-series,
calibre-db--get-tags, calibre-db--get-publishers,
calibre-db--get-author-books, calibre-db--get-tag-books,
calibre-db--get-publisher-books, calibre-db--get-series-books,
calibre-db--get-format-books, calibre--get-filter-items,
calibre-library--filter, calibre-library-clear-filters): Add new
functions to facilitate search.
(calibre-library--filters): Add new variable to store the currently
active search filters.
(calibre-library--refresh): Apply the active search filters before
displaying library contents.
* calibre-library.el: Require the new modules calibre-search and
calibre-virtual-library.
(calibre-library-open-book-other-window): Add function to open a book
in another window, for symmetry with Dired.
(calibre-library-mode-map): Add bindings for the new functions
calibre-library-open-book-other-window,
calibre-select-virtual-library, and calibre-search.
* calibre-search.el:
(calibre-chose-author, calibre-chose-publisher, calibre-chose-tag,
calibre-search--operation, calibre-library-search-author,
calibre-library-search-publisher, calibre-library-search-tag,
calibre-library-clear-last-search): Add new functions to facilitate
search.
(calibre-search): Add a transient to provide UI for search.
* calibre-virtual-library.el:
(calibre-virtual-libraries): Add new defcustom to store virtual
libraries.
(calibre-select-virtual-library): Add new function to select virtual
libraries.
* calibre.el: Bump package version.
---
README | 2 +
calibre-db.el | 104 ++++++++++++++++++++++++++++++++++++++++++++-
calibre-library.el | 12 +++++-
calibre-search.el | 95 +++++++++++++++++++++++++++++++++++++++++
calibre-virtual-library.el | 67 +++++++++++++++++++++++++++++
calibre.el | 2 +-
6 files changed, 279 insertions(+), 3 deletions(-)
diff --git a/README b/README
index e1c6597d65..3ca2423cdb 100644
--- a/README
+++ b/README
@@ -6,6 +6,8 @@ Calibre.el is a package for interacting with Calibre
include:
- Easily switch between multiple libraries
- List the contents of your library
+- Filter the contents of your library based on various metadata
+- Create Virtual Libraries by giving names to sets of filters
- Add and remove books from your library
- Read your books in Emacs
diff --git a/calibre-db.el b/calibre-db.el
index 5ced64be90..9bbb623f7a 100644
--- a/calibre-db.el
+++ b/calibre-db.el
@@ -87,6 +87,26 @@ WHERE books.id = ?"
"SELECT format FROM data WHERE book = ?"
`[,id]))))
+(defun calibre-db--get-authors ()
+ "Return a list of the authors in the active library."
+ (sqlite-select (calibre--db)
+ "SELECT name FROM authors;"))
+
+(defun calibre-db--get-series ()
+ "Return a list of the series in the active library."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT name FROM series;")))
+
+(defun calibre-db--get-tags ()
+ "Return a list of the tags in the active library."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT name FROM tags;")))
+
+(defun calibre-db--get-publishers ()
+ "Return a list of the publishers in the active library."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT name FROM publishers;")))
+
(defvar calibre--db nil)
(defun calibre--db ()
"Return the metadata database."
@@ -112,6 +132,79 @@ FROM books
LEFT JOIN books_series_link sl ON books.id = sl.book
LEFT JOIN series ON sl.series = series.id;"))))
+(defun calibre-db--get-author-books (author)
+ "Return the id's of books written by AUTHOR."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT book
+FROM books_authors_link al
+LEFT JOIN authors a ON al.author = a.id
+WHERE a.name = ?" `[,author])))
+
+(defun calibre-db--get-tag-books (tag)
+ "Return the id's of books tagged with TAG."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT book
+FROM books_tags_link tl
+LEFT JOIN tags t ON tl.tag = t.id
+WHERE t.name = ?" `[,tag])))
+
+(defun calibre-db--get-publisher-books (publisher)
+ "Return the id's of books published by PUBLISHER."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT book
+FROM books_publishers_link pl
+LEFT JOIN publishers p ON pl.publisher = p.id
+WHERE p.name = ?" `[,publisher])))
+
+(defun calibre-db--get-series-books (series)
+ "Return the id's of books that are part of SERIES."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT book
+FROM books_series_link sl
+LEFT JOIN series s ON sl.publisher = s.id
+WHERE s.name = ?" `[,series])))
+
+(defun calibre-db--get-format-books (format)
+ "Return the id's of books available in FORMAT."
+ (flatten-list (sqlite-select (calibre--db)
+ "SELECT book
+FROM data
+WHERE format = ?" `[,format])))
+
+(defun calibre--get-filter-items (filter)
+ "Return the id's of books matching FILTER."
+ (seq-let (_ field value) filter
+ (cl-case field
+ (author (calibre-db--get-author-books value))
+ (tag (calibre-db--get-tag-books value))
+ (publisher (calibre-db--get-publisher-books value))
+ (series (calibre-db--get-series-books value))
+ (format (calibre-db--get-format-books (upcase (symbol-name value)))))))
+
+(defun calibre-library--filter (filters books)
+ "Return those books in BOOKS that match FILTERS.
+FILTERS should be a list of vectors, for the exact contents see
+`calibre-virtual-libraries'."
+ (let* ((include (cl-remove-if-not (lambda (f) (eq (elt f 0) '+)) filters))
+ (exclude (cl-remove-if-not (lambda (f) (eq (elt f 0) '-)) filters))
+ (include-ids (when include
+ (cl-reduce #'cl-intersection
+ (mapcar #'calibre--get-filter-items
include))))
+ (exclude-ids (when exclude
+ (cl-reduce #'cl-union
+ (mapcar #'calibre--get-filter-items
exclude)))))
+ (cl-remove-if (lambda (b)
+ (seq-find (lambda (id)
+ (= id (calibre-book-id b)))
+ exclude-ids))
+ (if include-ids
+ (cl-remove-if-not (lambda (b)
+ (seq-find (lambda (id)
+ (= id (calibre-book-id b)))
+ include-ids))
+ books)
+ books))))
+
(defvar calibre--books nil)
(defun calibre--books (&optional force)
"Return the in memory list of books.
@@ -120,6 +213,14 @@ If FORCE is non-nil the list is refreshed from the
database."
(setf calibre--books (calibre-db--get-books)))
calibre--books)
+(defvar calibre-library--filters nil)
+
+(defun calibre-library-clear-filters ()
+ "Clear all active filters."
+ (interactive)
+ (setf calibre-library--filters nil)
+ (calibre-library--refresh))
+
(defvar calibre--library nil
"The active library.")
@@ -150,7 +251,8 @@ If FORCE is non-nil fetch book data from the database."
(with-current-buffer buffer
(setf tabulated-list-entries
(mapcar #'calibre-book--print-info
- (calibre--books force)))
+ (calibre-library--filter calibre-library--filters
+ (calibre--books force))))
(tabulated-list-print)))))
(defun calibre-library--set-header ()
diff --git a/calibre-library.el b/calibre-library.el
index 59a0fb09b0..9c2c70660a 100644
--- a/calibre-library.el
+++ b/calibre-library.el
@@ -26,6 +26,8 @@
(require 'dired)
(require 'calibre-db)
(require 'calibre-book)
+(require 'calibre-search)
+(require 'calibre-virtual-library)
;;;###autoload
(defun calibre-library-add-book (file)
@@ -114,6 +116,11 @@ ARGS should be a list of strings. SENTINEL is a process
sentinel to install."
(interactive (list (tabulated-list-get-id)) calibre-library-mode)
(find-file (calibre-book--file book (calibre-book--pick-format book))))
+(defun calibre-library-open-book-other-window (book)
+ "Open BOOK in its preferred format."
+ (interactive (list (tabulated-list-get-id)) calibre-library-mode)
+ (find-file-other-window (calibre-book--file book (calibre-book--pick-format
book))))
+
(defvar-keymap calibre-library-mode-map
:doc "Local keymap for Calibre Library buffers."
:parent tabulated-list-mode-map
@@ -121,7 +128,10 @@ ARGS should be a list of strings. SENTINEL is a process
sentinel to install."
"u" #'calibre-library-mark-unmark
"x" #'calibre-library-execute
"a" #'calibre-library-add-book
- "RET" #'calibre-library-open-book)
+ "v" #'calibre-select-virtual-library
+ "s" #'calibre-search
+ "RET" #'calibre-library-open-book
+ "o" #'calibre-library-open-book-other-window)
(define-derived-mode calibre-library-mode tabulated-list-mode
(setf tabulated-list-padding 2)
diff --git a/calibre-search.el b/calibre-search.el
new file mode 100644
index 0000000000..4c30203106
--- /dev/null
+++ b/calibre-search.el
@@ -0,0 +1,95 @@
+;;; calibre-search.el --- Filter books based on search criteria. -*-
lexical-binding: t; -*-
+;; Copyright (C) 2023 Kjartan Óli Águstsson
+
+;; This file is part of calibre.el.
+
+;; calibre.el is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; calibre.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with calibre.el. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;; This file provides an interface for filtering the current library
+;; on criteria such as Author, Publisher, Tags, etc.
+
+;;; Code:
+(require 'transient)
+(require 'calibre-db)
+
+(defun calibre-chose-author ()
+ "Prompt the user to select an author."
+ (interactive)
+ (completing-read "Author: " (calibre-db--get-authors)))
+
+(defun calibre-chose-publisher ()
+ "Prompt the user to select a publisher."
+ (interactive)
+ (completing-read "Author: " (calibre-db--get-publishers)))
+
+(defun calibre-chose-tag ()
+ "Prompt the user to select a tag."
+ (interactive)
+ (completing-read "Tag: " (calibre-db--get-tags)))
+
+(defun calibre-search--operation (args)
+ "Return the appropriate symbol for a filter operation.
+ARGS is the argument list of a transient command."
+ (if (cl-find "--exclude" args :test #'string=) '- '+))
+
+(defun calibre-library-search-author (author &optional args)
+ "Add a filter for AUTHOR.
+ARGS determines whether the created filter is inclusive or exclusive."
+ (interactive (list (calibre-chose-author)
+ (transient-args 'calibre-search)))
+ (setf calibre-library--filters (cons `[,(calibre-search--operation args)
author ,author]
+ calibre-library--filters))
+ (calibre-library--refresh))
+
+(defun calibre-library-search-publisher (publisher &optional args)
+ "Add a filter for PUBLISHER.
+ARGS determines whether the created filter is inclusive or exclusive."
+ (interactive (list (calibre-chose-publisher)
+ (transient-args 'calibre-search)))
+ (setf calibre-library--filters (cons `[,(calibre-search--operation args)
publisher ,publisher]
+ calibre-library--filters))
+ (calibre-library--refresh))
+
+(defun calibre-library-search-tag (tag &optional args)
+ "Add a filter for TAG.
+ARGS determines whether the created filter is inclusive or exclusive."
+ (interactive (list (calibre-chose-tag)
+ (transient-args 'calibre-search)))
+ (setf calibre-library--filters (cons `[,(calibre-search--operation args) tag
,tag]
+ calibre-library--filters))
+ (calibre-library--refresh))
+
+(defun calibre-library-clear-last-search ()
+ "Clear the last applied search filter."
+ (interactive)
+ (when calibre-library--filters
+ (setf calibre-library--filters (cdr calibre-library--filters)))
+ (calibre-library--refresh))
+
+(transient-define-prefix calibre-search ()
+ ""
+ :transient-suffix 'transient--do-call
+ ["Arguments"
+ ("-e" "Exclude" "--exclude")]
+ ["Search"
+ ("a" "Author" calibre-library-search-author)
+ ("p" "Publisher" calibre-library-search-publisher)
+ ("t" "Tag" calibre-library-search-tag)]
+ ["Misc"
+ ("u" "Undo" calibre-library-clear-last-search)
+ ("c" "Clear" calibre-library-clear-filters)
+ ("q" "Exit" transient-quit-one)])
+
+(provide 'calibre-search)
diff --git a/calibre-virtual-library.el b/calibre-virtual-library.el
new file mode 100644
index 0000000000..70f55357f4
--- /dev/null
+++ b/calibre-virtual-library.el
@@ -0,0 +1,67 @@
+;;; calibre-virtual-library.el --- Virtual library support -*-
lexical-binding: t; -*-
+
+;; Copyright (C) 2023 Kjartan Óli Águstsson
+
+;; This file is part of calibre.el.
+
+;; calibre.el is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; calibre.el is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with calibre.el. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;; This file provides virtual library support for calibre.el. A
+;; virtual library is a named set of predefined search filters.
+
+;;; Code:
+(require 'calibre-db)
+
+(defcustom calibre-virtual-libraries nil
+ "A list of predefined filters, i.e. Virtual Libraries.
+Each element is a cons cell (name . filters) where name is a
+string, and filters is a list of vectors [op field value]"
+ :type '(repeat :tag "Virtual Libraries"
+ (cons :tag "Virtual Library"
+ (string :tag "Name")
+ (repeat :tag "Filters"
+ (vector :tag "Filter"
+ (choice :tag "Operation"
+ (const :tag "Include" +)
+ (const :tag "Exclude" -))
+ (choice :tag "Field"
+ (const :tag "Author" author)
+ (const :tag "Publisher"
publisher)
+ (const :tag "Series" series)
+ (const :tag "Tag" tag)
+ (const :tag "Format" format))
+ (string :tag "Value")))))
+ :group 'calibre
+ :package-version '("calibre" . "1.1.0"))
+
+(defun calibre-select-virtual-library (arg &optional virtual-library)
+ "Prompt the user to select a virtual library.
+If called with a prefix argument clear the active virtual library."
+ (interactive "P")
+ (if arg
+ (calibre-library-clear-filters)
+ (setf calibre-library--filters
+ (alist-get
+ (if virtual-library
+ virtual-library
+ (completing-read "Virtual Library: "
+ (mapcar #'car calibre-virtual-libraries)
+ nil
+ t))
+ calibre-virtual-libraries nil nil #'string=)))
+ (calibre-library--refresh t))
+
+(provide 'calibre-virtual-library)
+;;; calibre-virtual-library.el ends here
diff --git a/calibre.el b/calibre.el
index c74062282f..3112bb0f73 100644
--- a/calibre.el
+++ b/calibre.el
@@ -4,7 +4,7 @@
;; Author: Kjartan Oli Agustsson <[email protected]>
;; Maintainer: Kjartan Oli Agustsson <[email protected]>
-;; Version: 1.0.5
+;; Version: 1.1.0
;; Package-Requires: ((emacs "29.1"))
;; URL: https://git.disroot.org/kjartanoli/calibre.el