1#!/usr/local/bin/python3.8
2# vim:fileencoding=utf-8
3# License: GPLv3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
4
5
6import os
7
8from calibre.db.cli import integers_from_string
9from calibre.db.errors import NoSuchFormat
10from calibre.library.save_to_disk import (
11    config, do_save_book_to_disk, get_formats, sanitize_args
12)
13from calibre.utils.formatter_functions import load_user_template_functions
14
15readonly = True
16version = 0  # change this if you change signature of implementation()
17
18
19def implementation(db, notify_changes, action, *args):
20    is_remote = notify_changes is not None
21    if action == 'all_ids':
22        return db.all_book_ids()
23    if action == 'setup':
24        book_id, formats = args
25        mi = db.get_metadata(book_id)
26        plugboards = db.pref('plugboards', {})
27        formats = get_formats(db.formats(book_id), formats)
28        return mi, plugboards, formats, db.library_id, db.pref(
29            'user_template_functions', []
30        )
31    if action == 'cover':
32        return db.cover(args[0])
33    if action == 'fmt':
34        book_id, fmt, dest = args
35        if is_remote:
36            return db.format(book_id, fmt)
37        db.copy_format_to(book_id, fmt, dest)
38
39
40def option_parser(get_parser, args):
41    parser = get_parser(
42        _(
43            '''\
44%prog export [options] ids
45
46Export the books specified by ids (a comma separated list) to the filesystem.
47The export operation saves all formats of the book, its cover and metadata (in
48an opf file). You can get id numbers from the search command.
49'''
50        )
51    )
52    parser.add_option(
53        '--all',
54        default=False,
55        action='store_true',
56        help=_('Export all books in database, ignoring the list of ids.')
57    )
58    parser.add_option(
59        '--to-dir',
60        default='.',
61        help=(
62            _('Export books to the specified folder. Default is') + ' %default'
63        )
64    )
65    parser.add_option(
66        '--single-dir',
67        default=False,
68        action='store_true',
69        help=_('Export all books into a single folder')
70    )
71    parser.add_option(
72        '--progress',
73        default=False,
74        action='store_true',
75        help=_('Report progress')
76    )
77    c = config()
78    for pref in ['asciiize', 'update_metadata', 'write_opf', 'save_cover']:
79        opt = c.get_option(pref)
80        switch = '--dont-' + pref.replace('_', '-')
81        parser.add_option(
82            switch,
83            default=True,
84            action='store_false',
85            help=opt.help + ' ' +
86            _('Specifying this switch will turn '
87              'this behavior off.'),
88            dest=pref
89        )
90
91    for pref in ['timefmt', 'template', 'formats']:
92        opt = c.get_option(pref)
93        switch = '--' + pref
94        parser.add_option(switch, default=opt.default, help=opt.help, dest=pref)
95
96    for pref in ('replace_whitespace', 'to_lowercase'):
97        opt = c.get_option(pref)
98        switch = '--' + pref.replace('_', '-')
99        parser.add_option(switch, default=False, action='store_true', help=opt.help)
100
101    return parser
102
103
104class DBProxy:
105
106    # Proxy to allow do_save_book_to_disk() to work with remote database
107
108    def __init__(self, dbctx):
109        self.dbctx = dbctx
110
111    def cover(self, book_id):
112        return self.dbctx.run('export', 'cover', book_id)
113
114    def copy_format_to(self, book_id, fmt, path):
115        fdata = self.dbctx.run('export', 'fmt', book_id, fmt, path)
116        if self.dbctx.is_remote:
117            if fdata is None:
118                raise NoSuchFormat(fmt)
119            with lopen(path, 'wb') as f:
120                f.write(fdata)
121
122
123def export(opts, dbctx, book_id, dest, dbproxy, length, first):
124    mi, plugboards, formats, library_id, template_funcs = dbctx.run(
125        'export', 'setup', book_id, opts.formats
126    )
127    if dbctx.is_remote and first:
128        load_user_template_functions(library_id, template_funcs)
129    return do_save_book_to_disk(
130        dbproxy, book_id, mi, plugboards, formats, dest, opts, length
131    )
132
133
134def main(opts, args, dbctx):
135    if len(args) < 1 and not opts.all:
136        raise SystemExit(_('You must specify some ids or the %s option') % '--all')
137    if opts.all:
138        book_ids = dbctx.run('export', 'all_ids')
139    else:
140        book_ids = set()
141        for arg in args:
142            book_ids |= set(integers_from_string(arg))
143    dest = os.path.abspath(os.path.expanduser(opts.to_dir))
144    dbproxy = DBProxy(dbctx)
145    dest, opts, length = sanitize_args(dest, opts)
146    total = len(book_ids)
147    for i, book_id in enumerate(book_ids):
148        export(opts, dbctx, book_id, dest, dbproxy, length, i == 0)
149        if opts.progress:
150            num = i + 1
151            print('\r  {:.0%} [{}/{}]'.format(num / total, num, total), end=' '*20)
152    if opts.progress:
153        print()
154    return 0
155