from calibre import terminal_controller, preferred_encoding, prints
from calibre.utils.config import OptionParser, prefs, tweaks
from calibre.ebooks.metadata.meta import get_metadata
from calibre.library.database2 import LibraryDatabase2
from calibre.ebooks.metadata.opf2 import OPFCreator, OPF
from calibre.utils.date import isoformat
FIELDS = set([
'title',
'authors',
'author_sort',
'publisher',
'rating',
'timestamp',
'size',
'tags',
'comments',
'series',
'series_index',
'formats',
'isbn',
'uuid',
'pubdate',
'cover'])
def send_message(msg = ''):
prints('Notifying calibre of the change')
RC = RC
import calibre.utils.ipc
import time
t = RC(print_error = False)
t.start()
time.sleep(3)
if t.done:
t.conn.send('refreshdb:' + msg)
t.conn.close()
def get_parser(usage):
parser = OptionParser(usage)
go = parser.add_option_group('GLOBAL OPTIONS')
go.add_option('--library-path', '--with-library', default = None, help = _('Path to the calibre library. Default is to use the path stored in the settings.'))
return parser
def get_db(dbpath, options):
if options.library_path is not None:
dbpath = options.library_path
dbpath = os.path.abspath(dbpath)
return LibraryDatabase2(dbpath)
def do_list(db, fields, afields, sort_by, ascending, search_text, line_width, separator, prefix, subtitle = 'Books in the calibre database'):
if sort_by:
db.sort(sort_by, ascending)
if search_text:
db.search(search_text)
data = db.get_data_as_dict(prefix, authors_as_string = True)
fields = [
'id'] + fields
title_fields = fields
fields = [ _[1] if x[0] == '*' else x for x in fields ]
text = [ wrappers[i].wrap(unicode(record[field]).encode('utf-8')) for i, field in enumerate(fields) ]
lines = max(map(len, text))
for l in range(lines):
for i, field in enumerate(text):
ft = [] if l < len(text[i]) else ''
filler = '%*s' % (widths[i] - len(ft) - 1, '')
o.write(ft)
o.write(filler + separator)
print >>o
return o.getvalue()
def list_option_parser(db = None):
fields = set(FIELDS)
if db is not None:
for f in db.custom_column_label_map:
fields.add('*' + f)
parser = get_parser(_('%prog list [options]\n\nList the books available in the calibre database.\n'))
parser.add_option('-f', '--fields', default = 'title,authors', help = _('The fields to display when listing books in the database. Should be a comma separated list of fields.\nAvailable fields: %s\nDefault: %%default. The special field "all" can be used to select all fields. Only has effect in the text output format.') % ','.join(sorted(fields)))
parser.add_option('--sort-by', default = None, help = _('The field by which to sort the results.\nAvailable fields: %s\nDefault: %%default') % ','.join(FIELDS))
parser.add_option('--ascending', default = False, action = 'store_true', help = _('Sort results in ascending order'))
parser.add_option('-s', '--search', default = None, help = _('Filter the results by the search query. For the format of the search query, please see the search related documentation in the User Manual. Default is to do no filtering.'))
parser.add_option('-w', '--line-width', default = -1, type = int, help = _('The maximum width of a single line in the output. Defaults to detecting screen size.'))
parser.add_option('--separator', default = ' ', help = _('The string used to separate fields. Default is a space.'))
parser.add_option('--prefix', default = None, help = _('The prefix for all file paths. Default is the absolute path to the library folder.'))
func = None if one_book_per_directory else db.import_book_directory_multiple
dups = func(dir)
if not dups:
dups = []
dir_dups.extend(dups)
sys.stdout = sys.__stdout__
if add_duplicates:
for mi, formats in dir_dups:
db.import_book(mi, formats)
elif dir_dups or file_duplicates:
print >>sys.stderr, _('The following books were not added as they already exist in the database (see --duplicates option):')
for mi, formats in dir_dups:
title = mi.title
if isinstance(title, unicode):
title = title.encode(preferred_encoding)
print >>sys.stderr, '\t', title + ':'
for path in formats:
print >>sys.stderr, '\t\t ', path
if file_duplicates:
for path, mi in zip(file_duplicates[0], file_duplicates[2]):
title = mi.title
if isinstance(title, unicode):
title = title.encode(preferred_encoding)
print >>sys.stderr, '\t', title + ':'
print >>sys.stderr, '\t\t ', path
send_message()
finally:
sys.stdout = orig
def add_option_parser():
parser = get_parser(_('%prog add [options] file1 file2 file3 ...\n\nAdd the specified files as books to the database. You can also specify directories, see\nthe directory related options below.\n'))
parser.add_option('-1', '--one-book-per-directory', action = 'store_true', default = False, help = _('Assume that each directory has only a single logical book and that all files in it are different e-book formats of that book'))
parser.add_option('-d', '--duplicates', action = 'store_true', default = False, help = _('Add books to database even if they already exist. Comparison is done based on book titles.'))
parser.add_option('-e', '--empty', action = 'store_true', default = False, help = _('Add an empty book (a book with no formats)'))
parser.add_option('-t', '--title', default = None, help = _('Set the title of the added empty book'))
parser.add_option('-a', '--authors', default = None, help = _('Set the authors of the added empty book'))
parser.add_option('-i', '--isbn', default = None, help = _('Set the ISBN of the added empty book'))
return get_parser(_('%prog remove ids\n\nRemove the books identified by ids from the database. ids should be a comma separated list of id numbers (you can get id numbers by using the list command). For example, 23,34,57-85\n'))
return get_parser(_('%prog add_format [options] id ebook_file\n\nAdd the ebook in ebook_file to the available formats for the logical book identified by id. You can get id by using the list command. If the format already exists, it is replaced.\n'))
return get_parser(_('\n%prog remove_format [options] id fmt\n\nRemove the format fmt from the logical book identified by id. You can get id by using the list command. fmt should be a file extension like LRF or TXT or EPUB. If the logical book does not have fmt available, do nothing.\n'))
print >>sys.stderr, _('You must specify an id and a format')
return 1
id = int(args[1])
fmt = args[2].upper()
do_remove_format(get_db(dbpath, opts), id, fmt)
return 0
def do_show_metadata(db, id, as_opf):
if not db.has_id(id):
raise ValueError('Id #%d is not present in database.' % id)
db.has_id(id)
mi = db.get_metadata(id, index_is_id = True)
if as_opf:
mi = OPFCreator(os.getcwd(), mi)
mi.render(sys.stdout)
else:
print unicode(mi).encode(preferred_encoding)
def show_metadata_option_parser():
parser = get_parser(_('\n%prog show_metadata [options] id\n\nShow the metadata stored in the calibre database for the book identified by id.\nid is an id number from the list command.\n'))
parser.add_option('--as-opf', default = False, action = 'store_true', help = _('Print metadata in OPF form (XML)'))
return get_parser(_('\n%prog set_metadata [options] id /path/to/metadata.opf\n\nSet the metadata stored in the calibre database for the book identified by id\nfrom the OPF file metadata.opf. id is an id number from the list command. You\ncan get a quick feel for the OPF format by using the --as-opf switch to the\nshow_metadata command.\n'))
parser = get_parser(_('%prog export [options] ids\n\nExport the books specified by ids (a comma separated list) to the filesystem.\nThe export operation saves all formats of the book, its cover and metadata (in\nan opf file). You can get id numbers from the list command.\n'))
parser.add_option('--all', default = False, action = 'store_true', help = _('Export all books in database, ignoring the list of ids.'))
parser.add_option('--to-dir', default = '.', help = _('Export books to the specified directory. Default is') + ' %default')
parser.add_option('--single-dir', default = False, action = 'store_true', help = _('Export all books into a single directory'))
config = config
import calibre.library.save_to_disk
c = config()
for pref in [
'asciiize',
'update_metadata',
'write_opf',
'save_cover']:
opt = c.get_option(pref)
switch = '--dont-' + pref.replace('_', '-')
parser.add_option(switch, default = True, action = 'store_false', help = opt.help + ' ' + _('Specifying this switch will turn this behavior off.'), dest = pref)
for pref in [
'timefmt',
'template',
'formats']:
opt = c.get_option(pref)
switch = '--' + pref
parser.add_option(switch, default = opt.default, help = opt.help, dest = pref)
for pref in ('replace_whitespace', 'to_lowercase'):
opt = c.get_option(pref)
switch = '--' + pref.replace('_', '-')
parser.add_option(switch, default = False, action = 'store_true', help = opt.help)
num = db.create_custom_column(label, name, datatype, is_multiple, display = display)
prints('Custom column created with id: %d' % num)
def add_custom_column_option_parser():
CustomColumns = CustomColumns
import calibre.library.custom_columns
parser = get_parser(_('%prog add_custom_column [options] label name datatype\n\nCreate a custom column. label is the machine friendly name of the column. Should\nnot contain spaces or colons. name is the human friendly name of the column.\ndatatype is one of: {0}\n').format(', '.join(CustomColumns.CUSTOM_DATA_TYPES)))
parser.add_option('--is-multiple', default = False, action = 'store_true', help = _('This column stores tag like data (i.e. multiple comma separated values). Only applies if datatype is text.'))
parser.add_option('--display', default = '{}', help = _('A dictionary of options to customize how the data in this column will be interpreted.'))
return parser
def command_add_custom_column(args, dbpath):
import json
parser = add_custom_column_option_parser()
(opts, args) = parser.parse_args(args)
if len(args) < 3:
parser.print_help()
print
print >>sys.stderr, _('You must specify label, name and datatype')
parser.add_option(option.option, default = option.default, dest = option.dest, action = option.action, help = option.help)
continue
parser.add_option(option.option, default = option.default, dest = option.dest, help = option.help)
return plugin
def print_help(parser, log):
help = parser.format_help().encode(preferred_encoding, 'replace')
log(help)
def validate_command_line(parser, args, log):
if not len(args) or args[0].startswith('-'):
print_help(parser, log)
log.error("\n\nYou must specify a catalog output file of the form 'path/to/destination.extension'\nTo review options for an output format, type 'calibredb catalog <.extension> --help'\nFor example, 'calibredb catalog .xml --help'\n")
if file_extension not in available_catalog_formats():
print_help(parser, log)
log.error("No catalog plugin available for extension '%s'.\n" % file_extension + 'Catalog plugins available for %s\n' % ', '.join(available_catalog_formats()))
raise SystemExit(1)
file_extension not in available_catalog_formats()
return (output, file_extension)
log = Log()
parser = get_parser(_('\n %prog catalog /path/to/destination.(csv|epub|mobi|xml ...) [options]\n\n Export a catalog in format specified by path/to/destination extension.\n Options control how entries are displayed in the generated catalog ouput.\n '))
parser.add_option('-i', '--ids', default = None, dest = 'ids', help = _('Comma-separated list of database IDs to catalog.\nIf declared, --search is ignored.\nDefault: all'))
parser.add_option('-s', '--search', default = None, dest = 'search_text', help = _('Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\nDefault: no filtering'))
parser.add_option('-v', '--verbose', default = False, action = 'store_true', dest = 'verbose', help = _('Show detailed output information. Useful for debugging'))
prints('Data set to: %r' % db.get_custom(id_, label = col, index_is_id = True))
def set_custom_option_parser():
parser = get_parser(_('\n %prog set_custom [options] column id value\n\n Set the value of a custom column for the book identified by id.\n You can get a list of ids using the list command.\n You can get a list of custom column names using the custom_columns\n command.\n '))
parser.add_option('-a', '--append', default = False, action = 'store_true', help = _('If the column stores multiple values, append the specified values to the existing ones, instead of replacing them.'))
return parser
def command_set_custom(args, dbpath):
parser = set_custom_option_parser()
(opts, args) = parser.parse_args(args)
if len(args) < 3:
parser.print_help()
print
print >>sys.stderr, _('Error: You must specify a field name, id and value')
q = raw_input(_('You will lose all data in the column: %r. Are you sure (y/n)? ') % label)
if q.lower().strip() != _('y'):
return None
db.delete_custom_column(label = label)
prints('Column %r removed.' % label)
def remove_custom_column_option_parser():
parser = get_parser(_('\n %prog remove_custom_column [options] label\n\n Remove the custom column identified by label. You can see available\n columns with the custom_columns command.\n '))
parser.add_option('-f', '--force', default = False, action = 'store_true', help = _('Do not ask for confirmation'))
return parser
def command_remove_custom_column(args, dbpath):
parser = remove_custom_column_option_parser()
(opts, args) = parser.parse_args(args)
if len(args) < 1:
parser.print_help()
print
prints(_('Error: You must specify a column label'), file = sys.stderr)
parser = OptionParser(_('%%prog command [options] [arguments]\n\n%%prog is the command line interface to the calibre books database.\n\ncommand is one of:\n %s\n\nFor help on an individual command: %%prog command --help\n') % '\n '.join(COMMANDS))