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 GLib
14
15import sqlite3
16from threading import Lock
17
18from lollypop.sqlcursor import SqlCursor
19
20
21class History:
22    """
23        History manager, allow Lollypop to restore stats when a file is moved
24    """
25    __LOCAL_PATH = GLib.get_user_data_dir() + "/lollypop"
26    __DB_PATH = "%s/history.db" % __LOCAL_PATH
27    __LIMIT = 1000000  # Delete when limit is reached
28    __DELETE = 100     # How many elements to delete
29    __create_history = """CREATE TABLE history (
30                            id INTEGER PRIMARY KEY,
31                            name TEXT NOT NULL,
32                            duration INT NOT NULL,
33                            ltime INT NOT NULL,
34                            popularity INT NOT NULL,
35                            rate INT NOT NULL,
36                            mtime INT NOT NULL,
37                            album_rate INT NOT NULL,
38                            loved INT NOT NULL,
39                            album_loved INT NOT NULL,
40                            album_synced INT NOT NULL,
41                            album_popularity INT NOT NULL)"""
42
43    def __init__(self):
44        """
45            Init playlists manager
46        """
47        self.thread_lock = Lock()
48        # Create db schema
49        try:
50            with SqlCursor(self, True) as sql:
51                sql.execute(self.__create_history)
52        except:
53            pass
54        with SqlCursor(self, True) as sql:
55            result = sql.execute("SELECT COUNT(*)\
56                                  FROM history")
57            v = result.fetchone()
58            if v is not None and v[0] > self.__LIMIT:
59                sql.execute("DELETE FROM history\
60                             WHERE rowid IN (SELECT rowid\
61                                             FROM history\
62                                             LIMIT %s)" % self.__DELETE)
63                sql.execute("VACUUM")
64
65    def add(self, name, duration, popularity, rate, ltime, mtime, loved,
66            album_loved, album_popularity, album_rate, album_synced):
67        """
68            Add an item to history
69            @param name as str
70            @param duration as int
71            @param popularity as int
72            @param rate as int
73            @param ltime as int
74            @param mtime as int
75            @param loved as bool
76            @param album_loved as bool
77            @param album_popularity as int
78            @param album_rate as int
79            @param album_synced as int
80            @thread safe
81        """
82        with SqlCursor(self, True) as sql:
83            # Needed because of seconds to ms DB migration
84            # Value in DB is rounded version of Gstreamer value
85            duration //= 1000
86            if self.exists(name, duration):
87                sql.execute("UPDATE history\
88                             SET popularity=?,rate=?,ltime=?,mtime=?,loved=?,\
89                             album_loved=?,album_popularity=?,album_rate=?,\
90                             album_synced=?\
91                             WHERE name=? AND duration=?",
92                            (popularity, rate, ltime, mtime, loved,
93                             album_loved, album_popularity, album_rate,
94                             album_synced,
95                             name, duration))
96            else:
97                sql.execute("INSERT INTO history\
98                             (name, duration, popularity, rate, ltime, mtime,\
99                             loved, album_loved, album_popularity, album_rate,\
100                             album_synced)\
101                             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
102                            (name, duration, popularity, rate, ltime, mtime,
103                             loved, album_loved, album_popularity, album_rate,
104                             album_synced))
105
106    def get(self, name, duration):
107        """
108            Get stats for track with name and duration
109            @param name as str
110            @param duration as int
111            @return (popularity, ltime, mtime,
112                     loved album, album_popularity, album_synced)
113             as (int, int, int, int, int, int)
114        """
115        with SqlCursor(self) as sql:
116            duration //= 1000
117            result = sql.execute("SELECT popularity, rate, ltime, mtime,\
118                                  loved, album_loved, album_popularity,\
119                                  album_rate, album_synced\
120                                  FROM history\
121                                  WHERE name=?\
122                                  AND duration=?",
123                                 (name, duration))
124            v = result.fetchone()
125            if v is not None:
126                return v
127            return (0, 0, 0, 0, 0, 0, 0, 0, 0)
128
129    def exists(self, name, duration):
130        """
131            Return True if entry exists
132            @param name as str
133            @param duration as int
134            @return bool
135        """
136        with SqlCursor(self) as sql:
137            result = sql.execute("SELECT rowid\
138                                  FROM history\
139                                  WHERE name=?\
140                                  AND duration=?",
141                                 (name, duration))
142            v = result.fetchone()
143            if v is not None:
144                return True
145            else:
146                return False
147
148    def get_cursor(self):
149        """
150            Return a new sqlite cursor
151        """
152        try:
153            return sqlite3.connect(self.__DB_PATH, 600.0)
154        except:
155            exit(-1)
156
157#######################
158# PRIVATE             #
159#######################
160