1# Copyright (c) 2014-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
2# This program is free software: you can redistribute it and/or modify
3# it under the terms of the GNU General Public License as published by
4# the Free Software Foundation, either version 3 of the License, or
5# (at your option) any later version.
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9# GNU General Public License for more details.
10# You should have received a copy of the GNU General Public License
11# along with this program. If not, see <http://www.gnu.org/licenses/>.
12
13from gi.repository import Gio
14
15import sqlite3
16from threading import Lock
17
18from lollypop.define import CACHE_PATH
19from lollypop.sqlcursor import SqlCursor
20from lollypop.database import Database
21from lollypop.logger import Logger
22
23
24class CacheDatabase:
25    """
26        Cache calculation into database
27    """
28    DB_PATH = "%s/cache_v1.db" % CACHE_PATH
29
30    # SQLite documentation:
31    # In SQLite, a column with type INTEGER PRIMARY KEY
32    # is an alias for the ROWID.
33    # Here, we define an id INT PRIMARY KEY but never feed it,
34    # this make VACUUM not destroy rowids...
35    __create_duration = """CREATE TABLE duration (
36                            id TEXT PRIMARY KEY,
37                            album_id INT NOT NULL,
38                            duration INT NOT NULL DEFAULT 0)"""
39
40    def __init__(self):
41        """
42            Create database tables
43        """
44        self.thread_lock = Lock()
45        f = Gio.File.new_for_path(self.DB_PATH)
46        if not f.query_exists():
47            try:
48                d = Gio.File.new_for_path(CACHE_PATH)
49                if not d.query_exists():
50                    d.make_directory_with_parents()
51                # Create db schema
52                with SqlCursor(self, True) as sql:
53                    sql.execute(self.__create_duration)
54            except Exception as e:
55                Logger.error("DatabaseCache::__init__(): %s" % e)
56
57    def set_duration(self, album_id, album_hash, duration):
58        """
59            Set duration in cache for album
60            @param album_id as int
61            @param album_hash as str
62            @param duration as int
63        """
64        try:
65            with SqlCursor(self, True) as sql:
66                sql.execute("INSERT INTO duration (id, album_id, duration)\
67                             VALUES (?, ?, ?)",
68                            (album_hash, album_id, duration))
69        except Exception as e:
70            Logger.error("DatabaseCache::set_duration(): %s", e)
71
72    def get_duration(self, album_hash):
73        """
74            Get duration in cache for album
75            @param album_hash as str
76            @return int/None
77        """
78        try:
79            with SqlCursor(self) as sql:
80                result = sql.execute("SELECT duration\
81                                      FROM duration\
82                                      WHERE id=?",
83                                     (album_hash,))
84                v = result.fetchone()
85                if v is not None:
86                    return v[0]
87        except Exception as e:
88            Logger.error("DatabaseCache::get_duration(): %s", e)
89        return None
90
91    def clear_durations(self, album_id):
92        """
93            Clear durations for album_id
94            @param album_id as int
95        """
96        with SqlCursor(self, True) as sql:
97            sql.execute("DELETE FROM duration WHERE album_id=?",
98                        (album_id,))
99
100    def clear_table(self, table):
101        """
102            Clear table
103            @param table as str
104        """
105        with SqlCursor(self, True) as sql:
106            sql.execute('DELETE FROM "%s"' % table)
107
108    def clean(self, commit=True):
109        """
110            Clean cache
111            @param commit as bool
112        """
113        with SqlCursor(self, commit) as sql:
114            sql.execute('ATTACH DATABASE "%s" AS music' % Database.DB_PATH)
115            sql.execute("DELETE FROM duration WHERE duration.album_id NOT IN (\
116                            SELECT albums.rowid FROM music.albums)")
117
118    def get_cursor(self):
119        """
120            Return a new sqlite cursor
121        """
122        try:
123            c = sqlite3.connect(self.DB_PATH, 600.0)
124            return c
125        except:
126            exit(-1)
127
128#######################
129# PRIVATE             #
130#######################
131