3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
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.
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.
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/>.
22 #include "constants.h"
23 #include <QDesktopServices>
25 static const int DATABASE_VERSION = 1;
26 static const QString dbName = QLatin1String(Constants::UNIX_NAME) + ".db";
27 static Database *databaseInstance = 0;
29 Database::Database() {
31 QString dataLocation = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
32 QDir().mkpath(dataLocation);
33 dbLocation = dataLocation + "/" + dbName;
35 QMutexLocker locker(&lock);
37 if(QFile::exists(dbLocation)) {
39 int databaseVersion = getAttribute("version").toInt();
40 if (databaseVersion != DATABASE_VERSION)
41 qWarning("Wrong database version: %d", databaseVersion);
42 } else createDatabase();
45 Database::~Database() {
49 void Database::createDatabase() {
51 qWarning() << "Creating the database";
53 const QSqlDatabase db = getConnection();
55 QSqlQuery("create table subscriptions ("
56 "id integer primary key autoincrement,"
60 "description varchar,"
64 "checked integer," // last check for videos on YT APIs
65 "updated integer," // most recent video added
66 "watched integer," // last time the user watched this channel
67 "loaded integer," // last time channel metadata was loaded from YT APIs
68 "notify_count integer," // new videos since "watched"
69 "views integer)" // number of times the user watched this channel
71 QSqlQuery("create unique index idx_user_id on subscriptions(user_id)", db);
73 QSqlQuery("create table subscriptions_videos ("
74 "id integer primary key autoincrement,"
83 "description varchar,"
89 QSqlQuery("create unique index idx_video_id on subscriptions_videos(video_id)", db);
91 QSqlQuery("create table attributes (name varchar, value)", db);
92 QSqlQuery("insert into attributes (name, value) values ('version', "
93 + QString::number(DATABASE_VERSION) + ")", db);
96 QString Database::getDbLocation() {
97 static QString dataLocation = QDesktopServices::storageLocation(
98 QDesktopServices::DataLocation);
99 return dataLocation + "/" + dbName;
102 bool Database::exists() {
103 static bool fileExists = false;
105 fileExists = QFile::exists(getDbLocation());
109 Database& Database::instance() {
111 QMutexLocker locker(&mutex);
112 if (!databaseInstance) databaseInstance = new Database();
113 return *databaseInstance;
116 QSqlDatabase Database::getConnection() {
117 QThread *currentThread = QThread::currentThread();
118 if (!currentThread) {
119 qDebug() << "current thread is null";
120 return QSqlDatabase();
123 const QString threadName = currentThread->objectName();
124 // qDebug() << "threadName" << threadName << currentThread;
125 if (connections.contains(currentThread)) {
126 return connections.value(currentThread);
128 // qDebug() << "Creating db connection for" << threadName;
129 QSqlDatabase connection = QSqlDatabase::addDatabase("QSQLITE", threadName);
130 connection.setDatabaseName(dbLocation);
131 if(!connection.open()) {
132 qWarning() << QString("Cannot connect to database %1 in thread %2").arg(dbLocation, threadName);
134 connections.insert(currentThread, connection);
139 QVariant Database::getAttribute(QString name) {
140 QSqlQuery query("select value from attributes where name=?", getConnection());
141 query.bindValue(0, name);
143 bool success = query.exec();
144 if (!success) qDebug() << query.lastQuery() << query.boundValues().values() << query.lastError().text();
146 return query.value(0);
150 void Database::setAttribute(QString name, QVariant value) {
151 QSqlQuery query(getConnection());
152 query.prepare("update attributes set value=? where name=?");
153 query.bindValue(0, value);
154 query.bindValue(1, name);
155 bool success = query.exec();
156 if (!success) qDebug() << query.lastError().text();
160 * After calling this method you have to reacquire a valid instance using instance()
162 void Database::drop() {
163 /// closeConnections();
164 if (!QFile::remove(dbLocation)) {
165 qWarning() << "Cannot delete database" << dbLocation;
167 // fallback to delete records in tables
168 const QSqlDatabase db = getConnection();
170 if (!query.exec("select name from sqlite_master where type='table'")) {
171 qWarning() << query.lastQuery() << query.lastError().text();
174 while (query.next()) {
175 QString tableName = query.value(0).toString();
176 if (tableName.startsWith("sqlite_") || tableName == "attributes") continue;
177 QString dropSQL = "delete from " + tableName;
178 QSqlQuery query2(db);
179 if (!query2.exec(dropSQL))
180 qWarning() << query2.lastQuery() << query2.lastError().text();
183 query.exec("delete from sqlite_sequence");
186 if (databaseInstance) delete databaseInstance;
187 databaseInstance = 0;
190 void Database::closeConnections() {
191 foreach(QSqlDatabase connection, connections.values()) {
192 // qDebug() << "Closing connection" << connection;
198 void Database::closeConnection() {
199 QThread *currentThread = QThread::currentThread();
200 if (!connections.contains(currentThread)) return;
201 QSqlDatabase connection = connections.take(currentThread);
202 // qDebug() << "Closing connection" << connection;
206 void Database::shutdown() {
207 if (!databaseInstance) return;
208 QSqlQuery("vacuum", databaseInstance->getConnection());
209 databaseInstance->closeConnections();