~ubuntu-branches/debian/sid/calibre/sid

« back to all changes in this revision

Viewing changes to src/calibre/db/backend.py

  • Committer: Package Import Robot
  • Author(s): Martin Pitt
  • Date: 2014-05-14 18:17:50 UTC
  • mfrom: (1.5.10)
  • Revision ID: package-import@ubuntu.com-20140514181750-xyrxqa47dbw0qfhu
Tags: 1.36.0+dfsg-1
* New upstream release:
  - Fixes editing of metadata (Closes: #741638)

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
from calibre.utils.config import to_json, from_json, prefs, tweaks
28
28
from calibre.utils.date import utcfromtimestamp, parse_date
29
29
from calibre.utils.filenames import (
30
 
    is_case_sensitive, samefile, hardlink_file, ascii_filename, WindowsAtomicFolderMove, atomic_rename)
 
30
    is_case_sensitive, samefile, hardlink_file, ascii_filename,
 
31
    WindowsAtomicFolderMove, atomic_rename, remove_dir_if_empty)
31
32
from calibre.utils.magick.draw import save_cover_data_to
32
33
from calibre.utils.formatter_functions import load_user_template_functions
33
34
from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable,
102
103
 
103
104
    def __delitem__(self, key):
104
105
        dict.__delitem__(self, key)
105
 
        self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
 
106
        self.db.execute('DELETE FROM preferences WHERE key=?', (key,))
106
107
 
107
108
    def __setitem__(self, key, val):
108
109
        if self.disable_setting:
109
110
            return
110
111
        raw = self.to_raw(val)
111
 
        self.db.conn.execute('INSERT OR REPLACE INTO preferences (key,val) VALUES (?,?)', (key,
112
 
            raw))
 
112
        self.db.execute('INSERT OR REPLACE INTO preferences (key,val) VALUES (?,?)', (key, raw))
113
113
        dict.__setitem__(self, key, val)
114
114
 
115
115
    def set(self, key, val):
234
234
 
235
235
class Connection(apsw.Connection):  # {{{
236
236
 
237
 
    BUSY_TIMEOUT = 2000  # milliseconds
 
237
    BUSY_TIMEOUT = 10000  # milliseconds
238
238
 
239
239
    def __init__(self, path):
240
240
        apsw.Connection.__init__(self, path)
348
348
            os.makedirs(self.library_path)
349
349
        self.is_case_sensitive = is_case_sensitive(self.library_path)
350
350
 
351
 
        SchemaUpgrade(self.conn, self.library_path, self.field_metadata)
 
351
        SchemaUpgrade(self, self.library_path, self.field_metadata)
352
352
 
353
353
        # Guarantee that the library_id is set
354
354
        self.library_id
355
355
 
356
356
        # Fix legacy triggers and columns
357
 
        self.conn.execute('''
 
357
        self.execute('''
358
358
        DROP TRIGGER IF EXISTS author_insert_trg;
359
359
        CREATE TEMP TRIGGER author_insert_trg
360
360
            AFTER INSERT ON authors
527
527
                    'SELECT id FROM custom_columns WHERE mark_for_delete=1'):
528
528
                num = record[0]
529
529
                table, lt = self.custom_table_names(num)
530
 
                self.conn.execute('''\
 
530
                self.execute('''\
531
531
                        DROP INDEX   IF EXISTS {table}_idx;
532
532
                        DROP INDEX   IF EXISTS {lt}_aidx;
533
533
                        DROP INDEX   IF EXISTS {lt}_bidx;
544
544
                        '''.format(table=table, lt=lt)
545
545
                )
546
546
                self.prefs.set('update_all_last_mod_dates_on_start', True)
547
 
            self.conn.execute('DELETE FROM custom_columns WHERE mark_for_delete=1')
 
547
            self.execute('DELETE FROM custom_columns WHERE mark_for_delete=1')
548
548
 
549
549
        # Load metadata for custom columns
550
550
        self.custom_column_label_map, self.custom_column_num_map = {}, {}
600
600
                for data in remove:
601
601
                    prints('WARNING: Custom column %r not found, removing.' %
602
602
                            data['label'])
603
 
                    self.conn.execute('DELETE FROM custom_columns WHERE id=?',
 
603
                    self.execute('DELETE FROM custom_columns WHERE id=?',
604
604
                            (data['num'],))
605
605
 
606
606
        if triggers:
607
607
            with self.conn:
608
 
                self.conn.execute('''\
 
608
                self.execute('''\
609
609
                    CREATE TEMP TRIGGER custom_books_delete_trg
610
610
                        AFTER DELETE ON books
611
611
                        BEGIN
787
787
                self._conn = Connection(self.dbpath)
788
788
        return self._conn
789
789
 
 
790
    def execute(self, sql, bindings=None):
 
791
        try:
 
792
            return self.conn.cursor().execute(sql, bindings)
 
793
        except apsw.IOError:
 
794
            # This can happen if the computer was suspended see for example:
 
795
            # https://bugs.launchpad.net/bugs/1286522. Try to reopen the db
 
796
            if not self.conn.getautocommit():
 
797
                raise  # We are in a transaction, re-opening the db will fail anyway
 
798
            self.reopen(force=True)
 
799
            return self.conn.cursor().execute(sql, bindings)
 
800
 
 
801
    def executemany(self, sql, sequence_of_bindings):
 
802
        try:
 
803
            with self.conn:  # Disable autocommit mode, for performance
 
804
                return self.conn.cursor().executemany(sql, sequence_of_bindings)
 
805
        except apsw.IOError:
 
806
            # This can happen if the computer was suspended see for example:
 
807
            # https://bugs.launchpad.net/bugs/1286522. Try to reopen the db
 
808
            if not self.conn.getautocommit():
 
809
                raise  # We are in a transaction, re-opening the db will fail anyway
 
810
            self.reopen(force=True)
 
811
            with self.conn:  # Disable autocommit mode, for performance
 
812
                return self.conn.cursor().executemany(sql, sequence_of_bindings)
 
813
 
 
814
    def get(self, *args, **kw):
 
815
        ans = self.execute(*args)
 
816
        if kw.get('all', True):
 
817
            return ans.fetchall()
 
818
        try:
 
819
            return ans.next()[0]
 
820
        except (StopIteration, IndexError):
 
821
            return None
 
822
 
 
823
    def last_insert_rowid(self):
 
824
        return self.conn.last_insert_rowid()
 
825
 
790
826
    def custom_field_name(self, label=None, num=None):
791
827
        if label is not None:
792
828
            return self.field_metadata.custom_field_prefix + label
800
836
    def set_custom_column_metadata(self, num, name=None, label=None, is_editable=None, display=None):
801
837
        changed = False
802
838
        if name is not None:
803
 
            self.conn.execute('UPDATE custom_columns SET name=? WHERE id=?', (name, num))
 
839
            self.execute('UPDATE custom_columns SET name=? WHERE id=?', (name, num))
804
840
            changed = True
805
841
        if label is not None:
806
 
            self.conn.execute('UPDATE custom_columns SET label=? WHERE id=?', (label, num))
 
842
            self.execute('UPDATE custom_columns SET label=? WHERE id=?', (label, num))
807
843
            changed = True
808
844
        if is_editable is not None:
809
 
            self.conn.execute('UPDATE custom_columns SET editable=? WHERE id=?', (bool(is_editable), num))
 
845
            self.execute('UPDATE custom_columns SET editable=? WHERE id=?', (bool(is_editable), num))
810
846
            self.custom_column_num_map[num]['is_editable'] = bool(is_editable)
811
847
            changed = True
812
848
        if display is not None:
813
 
            self.conn.execute('UPDATE custom_columns SET display=? WHERE id=?', (json.dumps(display), num))
 
849
            self.execute('UPDATE custom_columns SET display=? WHERE id=?', (json.dumps(display), num))
814
850
            changed = True
815
851
        # Note: the caller is responsible for scheduling a metadata backup if necessary
816
852
        return changed
826
862
        normalized  = datatype not in ('datetime', 'comments', 'int', 'bool',
827
863
                'float', 'composite')
828
864
        is_multiple = is_multiple and datatype in ('text', 'composite')
829
 
        self.conn.execute(
 
865
        self.execute(
830
866
                ('INSERT INTO '
831
867
                'custom_columns(label,name,datatype,is_multiple,editable,display,normalized)'
832
868
                'VALUES (?,?,?,?,?,?,?)'),
968
1004
                '''.format(table=table),
969
1005
            ]
970
1006
        script = ' \n'.join(lines)
971
 
        self.conn.execute(script)
 
1007
        self.execute(script)
972
1008
        self.prefs.set('update_all_last_mod_dates_on_start', True)
973
1009
        return num
974
1010
    # }}}
975
1011
 
976
1012
    def delete_custom_column(self, label=None, num=None):
977
1013
        data = self.custom_field_metadata(label, num)
978
 
        self.conn.execute('UPDATE custom_columns SET mark_for_delete=1 WHERE id=?', (data['num'],))
 
1014
        self.execute('UPDATE custom_columns SET mark_for_delete=1 WHERE id=?', (data['num'],))
979
1015
 
980
 
    def close(self):
 
1016
    def close(self, force=False):
981
1017
        if getattr(self, '_conn', None) is not None:
982
 
            self._conn.close()
 
1018
            self._conn.close(force)
983
1019
            del self._conn
984
1020
 
985
 
    def reopen(self):
986
 
        self.close()
 
1021
    def reopen(self, force=False):
 
1022
        self.close(force)
987
1023
        self._conn = None
988
1024
        self.conn
989
1025
 
1019
1055
                    self.reopen()
1020
1056
 
1021
1057
    def vacuum(self):
1022
 
        self.conn.execute('VACUUM')
 
1058
        self.execute('VACUUM')
1023
1059
 
1024
1060
    @dynamic_property
1025
1061
    def user_version(self):
1029
1065
            return self.conn.get('pragma user_version;', all=False)
1030
1066
 
1031
1067
        def fset(self, val):
1032
 
            self.conn.execute('pragma user_version=%d'%int(val))
 
1068
            self.execute('pragma user_version=%d'%int(val))
1033
1069
 
1034
1070
        return property(doc=doc, fget=fget, fset=fset)
1035
1071
 
1133
1169
 
1134
1170
        def fset(self, val):
1135
1171
            self._library_id_ = unicode(val)
1136
 
            self.conn.execute('''
 
1172
            self.execute('''
1137
1173
                    DELETE FROM library_id;
1138
1174
                    INSERT INTO library_id (uuid) VALUES (?);
1139
1175
                    ''', (self._library_id_,))
1149
1185
        Read all data from the db into the python in-memory tables
1150
1186
        '''
1151
1187
 
1152
 
        with self.conn:  # Use a single transaction, to ensure nothing modifies
1153
 
                         # the db while we are reading
 
1188
        with self.conn:  # Use a single transaction, to ensure nothing modifies the db while we are reading
1154
1189
            for table in self.tables.itervalues():
1155
1190
                try:
1156
1191
                    table.read(self)
1496
1531
            return f.read()
1497
1532
 
1498
1533
    def remove_books(self, path_map, permanent=False):
1499
 
        self.conn.executemany(
 
1534
        self.executemany(
1500
1535
            'DELETE FROM books WHERE id=?', [(x,) for x in path_map])
1501
1536
        paths = {os.path.join(self.library_path, x) for x in path_map.itervalues() if x}
1502
1537
        paths = {x for x in paths if os.path.exists(x) and self.is_deletable(x)}
1503
1538
        if permanent:
1504
1539
            for path in paths:
1505
1540
                self.rmtree(path)
1506
 
                try:
1507
 
                    os.rmdir(os.path.dirname(path))
1508
 
                except OSError as e:
1509
 
                    if e.errno != errno.ENOTEMPTY:
1510
 
                        raise
 
1541
                remove_dir_if_empty(os.path.dirname(path), ignore_metadata_caches=True)
1511
1542
        else:
1512
1543
            delete_service().delete_books(paths, self.library_path)
1513
1544
 
1514
1545
    def add_custom_data(self, name, val_map, delete_first):
1515
1546
        if delete_first:
1516
 
            self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name, ))
1517
 
        self.conn.executemany(
 
1547
            self.execute('DELETE FROM books_plugin_data WHERE name=?', (name, ))
 
1548
        self.executemany(
1518
1549
            'INSERT OR REPLACE INTO books_plugin_data (book, name, val) VALUES (?, ?, ?)',
1519
1550
            [(book_id, name, json.dumps(val, default=to_json))
1520
1551
                    for book_id, val in val_map.iteritems()])
1530
1561
        if len(book_ids) == 1:
1531
1562
            bid = next(iter(book_ids))
1532
1563
            ans = {book_id:safe_load(val) for book_id, val in
1533
 
                   self.conn.execute('SELECT book, val FROM books_plugin_data WHERE book=? AND name=?', (bid, name))}
 
1564
                   self.execute('SELECT book, val FROM books_plugin_data WHERE book=? AND name=?', (bid, name))}
1534
1565
            return ans or {bid:default}
1535
1566
 
1536
1567
        ans = {}
1537
 
        for book_id, val in self.conn.execute(
 
1568
        for book_id, val in self.execute(
1538
1569
            'SELECT book, val FROM books_plugin_data WHERE name=?', (name,)):
1539
1570
            if not book_ids or book_id in book_ids:
1540
1571
                val = safe_load(val)
1543
1574
 
1544
1575
    def delete_custom_book_data(self, name, book_ids):
1545
1576
        if book_ids:
1546
 
            self.conn.executemany('DELETE FROM books_plugin_data WHERE book=? AND name=?',
 
1577
            self.executemany('DELETE FROM books_plugin_data WHERE book=? AND name=?',
1547
1578
                                  [(book_id, name) for book_id in book_ids])
1548
1579
        else:
1549
 
            self.conn.execute('DELETE FROM books_plugin_data WHERE name=?', (name,))
 
1580
            self.execute('DELETE FROM books_plugin_data WHERE name=?', (name,))
1550
1581
 
1551
1582
    def get_ids_for_custom_book_data(self, name):
1552
 
        return frozenset(r[0] for r in self.conn.execute('SELECT book FROM books_plugin_data WHERE name=?', (name,)))
 
1583
        return frozenset(r[0] for r in self.execute('SELECT book FROM books_plugin_data WHERE name=?', (name,)))
1553
1584
 
1554
1585
    def conversion_options(self, book_id, fmt):
1555
1586
        for (data,) in self.conn.get('SELECT data FROM conversion_options WHERE book=? AND format=?', (book_id, fmt.upper())):
1558
1589
 
1559
1590
    def has_conversion_options(self, ids, fmt='PIPE'):
1560
1591
        ids = frozenset(ids)
1561
 
        self.conn.execute('DROP TABLE IF EXISTS conversion_options_temp; CREATE TEMP TABLE conversion_options_temp (id INTEGER PRIMARY KEY);')
1562
 
        self.conn.executemany('INSERT INTO conversion_options_temp VALUES (?)', [(x,) for x in ids])
1563
 
        for (book_id,) in self.conn.get(
1564
 
            'SELECT book FROM conversion_options WHERE format=? AND book IN (SELECT id FROM conversion_options_temp)', (fmt.upper(),)):
1565
 
            return True
1566
 
        return False
 
1592
        with self.conn:
 
1593
            self.execute('DROP TABLE IF EXISTS conversion_options_temp; CREATE TEMP TABLE conversion_options_temp (id INTEGER PRIMARY KEY);')
 
1594
            self.executemany('INSERT INTO conversion_options_temp VALUES (?)', [(x,) for x in ids])
 
1595
            for (book_id,) in self.conn.get(
 
1596
                'SELECT book FROM conversion_options WHERE format=? AND book IN (SELECT id FROM conversion_options_temp)', (fmt.upper(),)):
 
1597
                return True
 
1598
            return False
1567
1599
 
1568
1600
    def delete_conversion_options(self, book_ids, fmt):
1569
 
        self.conn.executemany('DELETE FROM conversion_options WHERE book=? AND format=?',
 
1601
        self.executemany('DELETE FROM conversion_options WHERE book=? AND format=?',
1570
1602
            [(book_id, fmt.upper()) for book_id in book_ids])
1571
1603
 
1572
1604
    def set_conversion_options(self, options, fmt):
1573
1605
        options = [(book_id, fmt.upper(), buffer(cPickle.dumps(data, -1))) for book_id, data in options.iteritems()]
1574
 
        self.conn.executemany('INSERT OR REPLACE INTO conversion_options(book,format,data) VALUES (?,?,?)', options)
 
1606
        self.executemany('INSERT OR REPLACE INTO conversion_options(book,format,data) VALUES (?,?,?)', options)
1575
1607
 
1576
1608
    def get_top_level_move_items(self, all_paths):
1577
1609
        items = set(os.listdir(self.library_path))
1627
1659
                pass
1628
1660
 
1629
1661
    def restore_book(self, book_id, path, formats):
1630
 
        self.conn.execute('UPDATE books SET path=? WHERE id=?', (path.replace(os.sep, '/'), book_id))
 
1662
        self.execute('UPDATE books SET path=? WHERE id=?', (path.replace(os.sep, '/'), book_id))
1631
1663
        vals = [(book_id, fmt, size, name) for fmt, size, name in formats]
1632
 
        self.conn.executemany('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', vals)
1633
 
   # }}}
1634
 
 
1635
 
 
 
1664
        self.executemany('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', vals)
 
1665
    # }}}