I do something close to that using an Action Chain similar to this code:
Code:
from calibre_plugins.action_chains.actions.base import ChainAction
class MarkLastBookInSeriesAction(ChainAction):
name = 'Mark Last Book In Series'
def run(self, gui, settings, chain):
db = gui.current_db
api = gui.current_db.new_api
book_ids = {}
series_names = api.all_field_names('series')
for series_name in series_names:
series_id = api.get_item_id('series', series_name)
books_in_series = api.books_for_field('series', series_id)
print(f"loaded {len(books_in_series)} books in series {series_name}")
book_id = self.last_book_in_series(api, books_in_series)
mark = 'last_book' if len(books_in_series) > 1 else 'only_book'
if book_id:
book_ids.update({book_id: mark})
db.set_marked_ids(book_ids)
# in practice setting the search likely belongs in its own link of the chain but this makes it less complicated to explain
gui.search.setEditText('marked:true')
gui.search.do_search()
def last_book_in_series(self, api, books_in_series):
indexes = api.all_field_for("series_index", books_in_series, None)
# primary works vs short stories...
# if 11.0 and 11.1 exist, do we really want the 11.1? probably not.
# also, ignore anything under 1. omnibus and prequels etc.
last_primary_work = None
last_minor_work = None
any_work = None
for book_id, i in sorted(indexes.items(), key=lambda kv: (kv[1], kv[0]), reverse=True):
if any_work is None:
any_work = book_id
if i < 1.0:
continue
if i.is_integer():
if last_primary_work is None:
last_primary_work = book_id # it would be safe to just return this now
else:
if last_minor_work is None:
last_minor_work = book_id
return last_primary_work or last_minor_work or any_work