using cYo.Projects.ComicRack.Engine.Database;
using cYo.Projects.ComicRack.Viewer;
using System;
using System.Collections.Specialized;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
namespace BCR
{
///
/// Description of Database.
///
public sealed class Database : IDisposable
{
private const int COMIC_DB_VERSION = 1;
private SQLiteConnection mConnection;
private string mFolder;
private const string DIRECTORY = "ComicRack BCR";
private static Database instance = new Database();
private int mVersion = 0;
private GlobalSettings _globalSettings = new GlobalSettings();
private Guid libraryGuid = Guid.Empty;
private Guid bcrGuid = Guid.Empty;
public GlobalSettings GlobalSettings { get { return _globalSettings; } }
public static Database Instance
{
get { return instance; }
}
public static string ConfigurationFolder { get { return DIRECTORY; } }
public Database()
{
mFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), DIRECTORY);
if (!Directory.Exists(mFolder))
{
Directory.CreateDirectory(mFolder);
}
string s = "cYo.Projects.ComicRack.Engine.Database.ComicLibraryListItem";
ComicListItem item = Program.Database.ComicLists.GetItems(false).FirstOrDefault((ComicListItem cli) => cli.GetType().ToString() == s);
if (item != null)
{
libraryGuid = item.Id;
}
s = "[BCR Users]";
item = Program.Database.ComicLists.GetItems(false).FirstOrDefault((ComicListItem cli) => cli.Name == s);
if (item == null)
{
// Add it
ComicListItemFolder bcrFolder = new ComicListItemFolder(s);
((ComicLibrary)Program.Database).ComicLists.Add(bcrFolder);
item = Program.Database.ComicLists.GetItems(false).FirstOrDefault((ComicListItem cli) => cli.Name == s);
}
if (item != null)
{
bcrGuid = item.Id;
}
}
public void Initialize()
{
try
{
mConnection = new SQLiteConnection(@"Data Source=" + mFolder + "\\bcr.s3db");
mConnection.Open();
}
catch (System.DllNotFoundException e)
{
Trace.WriteLine(String.Format("Exception: {0}", e));
MessageBox.Show("SQLite.Interop.dll seems to be missing. Aborting.", "Badaap Comic Reader Plugin", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
catch (SQLiteException e)
{
Trace.WriteLine(String.Format("Exception: {0}", e));
// error while opening/creating database
mConnection = null;
Trace.WriteLine("Failed to create/open the BCR database:");
Trace.WriteLine(e.ToString());
return;
}
// Check if the database is initialized by checking if the settings table exists.
object name = ExecuteScalar("SELECT name FROM sqlite_master WHERE type='table' AND name='settings';");
if (name == null)
{
// No settings table.
// Create the entire database.
mVersion = 0;
}
else
{
// Read version so we know if we must do a database update.
object version = ExecuteScalar("SELECT value FROM settings WHERE key='version';");
mVersion = Convert.ToInt32(version);
}
if (mVersion < 1)
{
// Create the database
using (SQLiteTransaction transaction = mConnection.BeginTransaction())
{
ExecuteNonQuery("CREATE TABLE settings(key TEXT PRIMARY KEY NOT NULL, value TEXT);");
ExecuteNonQuery("INSERT INTO settings (key,value) VALUES ('version','" + COMIC_DB_VERSION + "');");
ExecuteNonQuery("INSERT INTO settings (key,value) VALUES ('port','8080');");
ExecuteNonQuery(@"CREATE TABLE user(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
salt TEXT NOT NULL,
activity INTEGER NOT NULL DEFAULT (CURRENT_TIMESTAMP),
created INTEGER NOT NULL DEFAULT (CURRENT_TIMESTAMP),
fullname TEXT DEFAULT ''
);");
ExecuteNonQuery(@"CREATE TABLE user_settings(
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
open_current_comic_at_launch INTEGER DEFAULT 1,
open_next_comic INTEGER DEFAULT 1,
page_fit_mode INTEGER DEFAULT 1,
zoom_on_tap INTEGER DEFAULT 1,
toggle_paging_bar INTEGER DEFAULT 2,
use_page_turn_drag INTEGER DEFAULT 1,
page_turn_drag_threshold INTEGER DEFAULT 75,
use_page_change_area INTEGER DEFAULT 1,
page_change_area_width INTEGER DEFAULT 50,
use_comicrack_progress INTEGER DEFAULT 0,
home_list_id TEXT DEFAULT ''
);");
/*
ExecuteNonQuery(@"CREATE TABLE user_custom_settings(
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
key TEXT NOT NULL,
value TEXT
);");
*/
ExecuteNonQuery(@"CREATE TABLE user_apikeys(
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
apikey TEXT NOT NULL,
created INTEGER NOT NULL DEFAULT (CURRENT_TIMESTAMP),
activity INTEGER NOT NULL DEFAULT (CURRENT_TIMESTAMP)
);");
// TODO: set hook on the deletion of a comic book from the library
ExecuteNonQuery(@"CREATE TABLE comic_progress(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
comic_id TEXT NOT NULL,
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
date_last_read TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
current_page INTEGER DEFAULT 0,
last_page_read INTEGER DEFAULT 0
);");
/*
ExecuteNonQuery(@"CREATE TABLE comic_favorites(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
comic_id TEXT NOT NULL
);");
ExecuteNonQuery(@"CREATE TABLE series_favorites(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
series TEXT NOT NULL
);");
// type, favorite:
// 0, comic guid
// 1, series name
// 2, writer/colorer etc name
// 3, character name
ExecuteNonQuery(@"CREATE TABLE favorites(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
favorite TEXT NOT NULL,
type INTEGER
);");
*/
// Automatically create a user_settings record when a user is added.
ExecuteNonQuery("CREATE TRIGGER AddUserSettingsTrigger AFTER INSERT ON user BEGIN INSERT INTO user_settings (user_id) VALUES (NEW.id); END;");
// Automatically invalidate all user sessions when the user changes its username or password
ExecuteNonQuery("CREATE TRIGGER InvalidateApiKeys AFTER UPDATE ON user WHEN (NEW.username != OLD.username) OR (NEW.password != OLD.password) OR (NEW.salt != OLD.salt) BEGIN DELETE FROM user_apikeys WHERE user_id=NEW.id; END;");
// Create default user
UserDatabase.AddUser("user", "password");
transaction.Commit();
}
}
if (mVersion < COMIC_DB_VERSION)
{
ExecuteNonQuery("UPDATE settings SET value='" + COMIC_DB_VERSION + "' WHERE key='version';");
}
GlobalSettings.Initialize();
Validate();
}
///
/// Check if the database contains invalid references to data in the ComicRack database.
/// Remove those references.
///
private void Validate()
{
// Check if the lists referenced by users still exist.
// Check if the comics referenced by users still exist.
// TODO: provide user feedback in startup screen of BCR ?
}
public long GetLastInsertRowId()
{
return mConnection.LastInsertRowId;
}
///
/// Simple wrapper
///
/// The SQL statement to execute.
/// number of affected rows
public int ExecuteNonQuery(string sql)
{
using (SQLiteCommand command = mConnection.CreateCommand())
{
command.CommandText = sql;
return command.ExecuteNonQuery();
}
}
///
/// Simple wrapper
///
/// The SQL statement to execute.
/// First column of first row of the query result.
public object ExecuteScalar(string sql)
{
using (SQLiteCommand command = mConnection.CreateCommand())
{
command.CommandText = sql;
return command.ExecuteScalar();
}
}
///
/// Simple wrapper
///
/// The SQL statement to execute.
/// SQLiteDataReader with the query result.
public SQLiteDataReader ExecuteReader(string sql)
{
using (SQLiteCommand command = mConnection.CreateCommand())
{
command.CommandText = sql;
return command.ExecuteReader();
}
}
///
/// Executes a query and returns the first row.
///
/// The SQL statement to execute.
/// The first row of the query result.
public NameValueCollection QuerySingle(string sql)
{
using (SQLiteCommand command = mConnection.CreateCommand())
{
command.CommandText = sql;
using (SQLiteDataReader reader = command.ExecuteReader())
{
if (reader.Read())
{
return reader.GetValues();
}
}
}
return null;
}
public void Dispose()
{
if (mConnection != null)
{
mConnection.Dispose();
mConnection = null;
}
}
~Database()
{
Dispose();
}
}
}