]> git.sur5r.net Git - minitube/blob - src/database.cpp
e49133b1e829d698fe5076c9b2786992bbc7150e
[minitube] / src / database.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Minitube is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Minitube is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Minitube.  If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "database.h"
22 #include "constants.h"
23 #include "compatibility/pathsservice.h"
24 #include <QtDebug>
25
26 static const int DATABASE_VERSION = 1;
27 static const QString dbName = QLatin1String(Constants::UNIX_NAME) + ".db";
28 static Database *databaseInstance = 0;
29
30 Database::Database() {
31     QString dataLocation = Paths::getDataLocation();
32
33     if (!QDir().mkpath(dataLocation)) {
34       qCritical() << "Failed to create directory " << dataLocation;
35     }
36     dbLocation = dataLocation + "/" + dbName;
37
38     QMutexLocker locker(&lock);
39
40     if (QFile::exists(dbLocation)) {
41         // check db version
42         int databaseVersion = getAttribute("version").toInt();
43         if (databaseVersion > DATABASE_VERSION)
44             qWarning("Wrong database version: %d", databaseVersion);
45
46         if (!getAttribute("channelIdFix").toBool())
47             fixChannelIds();
48
49     } else createDatabase();
50 }
51
52 Database::~Database() {
53     closeConnections();
54 }
55
56 void Database::createDatabase() {
57
58     qWarning() << "Creating the database";
59
60     const QSqlDatabase db = getConnection();
61
62     QSqlQuery("create table subscriptions ("
63               "id integer primary key autoincrement,"
64               "user_id varchar," // this is really channel_id
65               "user_name varchar," // obsolete yt2 username
66               "name varchar," // this is really channel_title
67               "description varchar,"
68               "thumb_url varchar,"
69               "country varchar,"
70               "added integer,"
71               "checked integer," // last check for videos on YT APIs
72               "updated integer," // most recent video added
73               "watched integer," // last time the user watched this channel
74               "loaded integer," // last time channel metadata was loaded from YT APIs
75               "notify_count integer," // new videos since "watched"
76               "views integer)" // number of times the user watched this channel
77               , db);
78     QSqlQuery("create unique index idx_user_id on subscriptions(user_id)", db);
79
80     QSqlQuery("create table subscriptions_videos ("
81               "id integer primary key autoincrement,"
82               "video_id varchar,"
83               "channel_id integer," // this is really subscription_id
84               "published integer,"
85               "added integer,"
86               "watched integer,"
87               "title varchar,"
88               "author varchar," // this is really channel_title
89               "user_id varchar," // this is really channel_id
90               "description varchar,"
91               "url varchar,"
92               "thumb_url varchar,"
93               "views integer,"
94               "duration integer)"
95               , db);
96     QSqlQuery("create unique index idx_video_id on subscriptions_videos(video_id)", db);
97
98     QSqlQuery("create table attributes (name varchar, value)", db);
99     QSqlQuery("insert into attributes (name, value) values ('version', "
100               + QString::number(DATABASE_VERSION) + ")", db);
101 }
102
103 // static
104 QString Database::getDbLocation() {
105     return Paths::getDataLocation() + "/" + dbName;
106 }
107
108 // static
109 bool Database::exists() {
110     static bool fileExists = false;
111     if (!fileExists)
112         fileExists = QFile::exists(getDbLocation());
113     return fileExists;
114 }
115
116 // static
117 Database& Database::instance() {
118     static QMutex mutex;
119     QMutexLocker locker(&mutex);
120     if (!databaseInstance) databaseInstance = new Database();
121     return *databaseInstance;
122 }
123
124 QSqlDatabase Database::getConnection() {
125     QThread *currentThread = QThread::currentThread();
126     if (!currentThread) {
127         qDebug() << "current thread is null";
128         return QSqlDatabase();
129     }
130
131     const QString threadName = currentThread->objectName();
132     // qDebug() << "threadName" << threadName << currentThread;
133     if (connections.contains(currentThread)) {
134         return connections.value(currentThread);
135     } else {
136         // qDebug() << "Creating db connection for" << threadName;
137         QSqlDatabase connection = QSqlDatabase::addDatabase("QSQLITE", threadName);
138         connection.setDatabaseName(dbLocation);
139         if(!connection.open()) {
140             qWarning() << QString("Cannot connect to database %1 in thread %2").arg(dbLocation, threadName);
141         }
142         connections.insert(currentThread, connection);
143         return connection;
144     }
145 }
146
147 QVariant Database::getAttribute(QString name) {
148     QSqlQuery query("select value from attributes where name=?", getConnection());
149     query.bindValue(0, name);
150
151     bool success = query.exec();
152     if (!success) qDebug() << query.lastQuery() << query.boundValues().values() << query.lastError().text();
153     if (query.next())
154         return query.value(0);
155     return QVariant();
156 }
157
158 void Database::setAttribute(QString name, QVariant value) {
159     QSqlQuery query(getConnection());
160     query.prepare("insert or replace into attributes (name, value) values (?,?)");
161     query.bindValue(0, name);
162     query.bindValue(1, value);
163     bool success = query.exec();
164     if (!success) qWarning() << query.lastError().text();
165 }
166
167 void Database::fixChannelIds() {
168     if (!getConnection().transaction())
169         qWarning() << "Transaction failed" << __PRETTY_FUNCTION__;
170
171     qWarning() << "Fixing channel ids";
172
173     QSqlQuery query(getConnection());
174     bool success = query.exec("update subscriptions set user_id='UC' || user_id where user_id not like 'UC%'");
175     if (!success) qWarning() << query.lastError().text();
176
177     query = QSqlQuery(getConnection());
178     success = query.exec("update subscriptions_videos set user_id='UC' || user_id where user_id not like 'UC%'");
179     if (!success) qWarning() << query.lastError().text();
180
181     setAttribute("channelIdFix", 1);
182
183     if (!getConnection().commit())
184         qWarning() << "Commit failed" << __PRETTY_FUNCTION__;
185 }
186
187 /**
188   * After calling this method you have to reacquire a valid instance using instance()
189   */
190 void Database::drop() {
191     /// closeConnections();
192     if (!QFile::remove(dbLocation)) {
193         qWarning() << "Cannot delete database" << dbLocation;
194
195         // fallback to delete records in tables
196         const QSqlDatabase db = getConnection();
197         QSqlQuery query(db);
198         if (!query.exec("select name from sqlite_master where type='table'")) {
199             qWarning() << query.lastQuery() << query.lastError().text();
200         }
201
202         while (query.next()) {
203             QString tableName = query.value(0).toString();
204             if (tableName.startsWith("sqlite_") || tableName == QLatin1String("attributes")) continue;
205             QString dropSQL = "delete from " + tableName;
206             QSqlQuery query2(db);
207             if (!query2.exec(dropSQL))
208                 qWarning() << query2.lastQuery() << query2.lastError().text();
209         }
210
211         query.exec("delete from sqlite_sequence");
212
213     }
214     if (databaseInstance) delete databaseInstance;
215     databaseInstance = 0;
216 }
217
218 void Database::closeConnections() {
219     foreach(QSqlDatabase connection, connections.values()) {
220         // qDebug() << "Closing connection" << connection;
221         connection.close();
222     }
223     connections.clear();
224 }
225
226 void Database::closeConnection() {
227     QThread *currentThread = QThread::currentThread();
228     if (!connections.contains(currentThread)) return;
229     QSqlDatabase connection = connections.take(currentThread);
230     // qDebug() << "Closing connection" << connection;
231     connection.close();
232 }
233
234 void Database::shutdown() {
235     if (!databaseInstance) return;
236     QSqlQuery("vacuum", databaseInstance->getConnection());
237     databaseInstance->closeConnections();
238 }