[gnome-games] application: Add support for Favorites collection
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games] application: Add support for Favorites collection
- Date: Thu, 16 Jul 2020 14:49:58 +0000 (UTC)
commit fee1357d1e91240b769cda7d556661e43bc78b37
Author: Neville <nevilleantony98 gmail com>
Date: Thu Jul 2 10:58:31 2020 +0530
application: Add support for Favorites collection
Add database queries for supporting a favorites collection.
Add versioning support to Migrator using the existing .version file.
This is used to migrate the database to support favorites collections
by altering the games table to have a new is_favorite column.
Add and modify database queries to support favorites collection.
Move query statement preparation to a public prepare_statements() which
must be called after creation of database. This provides better database
migration support.
Migration will now be applied before creation of GameCollection.
src/core/migrator.vala | 115 +++++++++++++++++++++++++++++++++++++------
src/database/database.vala | 118 +++++++++++++++++++++++++++++++++++++++------
src/ui/application.vala | 15 +++---
3 files changed, 208 insertions(+), 40 deletions(-)
---
diff --git a/src/core/migrator.vala b/src/core/migrator.vala
index 44b73dbc..eab75891 100644
--- a/src/core/migrator.vala
+++ b/src/core/migrator.vala
@@ -1,9 +1,103 @@
// This file is part of GNOME Games. License: GPL-3.0+.
-public class Games.Migrator : Object {
+class Games.Migrator : Object {
+ // LATEST_VERSION should match the total number of migrations
+ private const uint LATEST_VERSION = 2;
+
+ private static uint version = 0;
+ private static bool skip_migration;
+
+ public static void bump_to_latest_version () {
+ info ("[Migrator]: Skipping migration");
+ while (version < LATEST_VERSION)
+ bump_version ();
+
+ skip_migration = true;
+ }
+
// Returns true if the migration wasn't necessary or
// if it was performed succesfully
- public static bool apply_migration_if_necessary () {
+ public static bool apply_migration_if_necessary (Database database) {
+ if (skip_migration)
+ return true;
+
+ version = get_version ();
+
+ if (version == 0) {
+ if (!apply_data_dir_migration ())
+ return false;
+
+ bump_version ();
+ }
+
+ if (version < 2) {
+ try {
+ database.apply_favorites_migration ();
+ }
+ catch (Error e) {
+ critical ("Failed to apply favorites migration: %s", e.message);
+ return false;
+ }
+
+ bump_version ();
+ }
+
+ return true;
+ }
+
+ private static uint get_version () {
+ var data_dir_path = Application.get_data_dir ();
+ var data_dir = File.new_for_path (data_dir_path);
+ var version_file = data_dir.get_child (".version");
+
+ if (version_file.query_exists ()) {
+ try {
+ var @file_input_stream = version_file.read ();
+ var data_input_stream = new DataInputStream (@file_input_stream);
+ string line;
+
+ // .version contains version number => version 2+
+ if ((line = data_input_stream.read_line ()) != null)
+ return uint.parse (line);
+
+ //has .version file but no version in it => version 1
+ return 1;
+ } catch (Error e) {
+ critical ("Failed to bump version: %s", e.message);
+ }
+ }
+
+ // no .version file => version 0
+ return 0;
+ }
+
+ public static void bump_version () {
+ var data_dir_path = Application.get_data_dir ();
+ var data_dir = File.new_for_path (data_dir_path);
+ var version_file = data_dir.get_child (".version");
+
+ if (version > 0) {
+ try {
+ version_file.replace_contents ((++version).to_string ().data, null, false,
FileCreateFlags.NONE, null);
+ } catch (Error e) {
+ critical ("Failed to bump version to %u: %s", version--, e.message);
+ }
+
+ return;
+ }
+
+ try {
+ version_file.create (FileCreateFlags.NONE);
+ }
+ catch (Error e) {
+ critical ("Failed to create .version file: %s", e.message);
+ return;
+ }
+
+ version++;
+ }
+
+ private static bool apply_data_dir_migration () {
var data_dir_path = Application.get_data_dir ();
var data_dir = File.new_for_path (data_dir_path);
@@ -12,14 +106,7 @@ public class Games.Migrator : Object {
var database_path = Application.get_database_path ();
string[] backup_excluded_files = { database_path, backup_archive_path };
- var version_file = data_dir.get_child (".version");
-
- // If the version file exists, there's no need
- // to apply the migration
- if (version_file.query_exists ())
- return true;
-
- info ("[Migrator]: Migration is necessary");
+ info ("[Migrator]: Data directory migration is necessary");
// Attempt to create a backup of the previous data
try {
@@ -34,7 +121,7 @@ public class Games.Migrator : Object {
try {
// The migration executes file I/O which may result in errors being
// thrown
- apply_migration (version_file);
+ apply_migration ();
}
catch (Error e) {
critical ("Migration failed: %s", e.message);
@@ -62,14 +149,10 @@ public class Games.Migrator : Object {
// Migration applied succesfully, deleting backup
delete_files_no_errors (backup_archive);
-
return true;
}
- private static void apply_migration (File version_file) throws Error {
- // Create the version file
- version_file.create (FileCreateFlags.NONE);
-
+ private static void apply_migration () throws Error {
// Create the savestates dir
var savestates_dir = File.new_for_path (get_savestates_dir_path ());
diff --git a/src/database/database.vala b/src/database/database.vala
index cffa937c..435a07eb 100644
--- a/src/database/database.vala
+++ b/src/database/database.vala
@@ -16,7 +16,8 @@ private class Games.Database : Object {
uid TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
platform TEXT NOT NULL,
- media_set TEXT NULL
+ media_set TEXT NULL,
+ is_favorite INTEGER NOT NULL DEFAULT 0
);
""";
@@ -54,11 +55,11 @@ private class Games.Database : Object {
""";
private const string GET_CACHED_GAME_QUERY = """
- SELECT uri, title, platform, media_set FROM games JOIN uris ON games.uid == uris.uid WHERE
games.uid == $UID;
+ SELECT uri, title, platform, media_set, is_favorite FROM games JOIN uris ON games.uid ==
uris.uid WHERE games.uid == $UID;
""";
private const string LIST_CACHED_GAMES_QUERY = """
- SELECT games.uid, uri, title, platform, media_set FROM games JOIN uris ON games.uid ==
uris.uid ORDER BY title;
+ SELECT games.uid, uri, title, platform, media_set, is_favorite FROM games JOIN uris ON
games.uid == uris.uid ORDER BY title;
""";
private const string ADD_GAME_RESOURCE_QUERY = """
@@ -69,6 +70,18 @@ private class Games.Database : Object {
SELECT EXISTS (SELECT 1 FROM game_resources WHERE uri=$URI LIMIT 1);
""";
+ private const string SET_IS_FAVORITE_QUERY = """
+ UPDATE games SET is_favorite = $IS_FAVORITE WHERE uid = $UID;
+ """;
+
+ private const string IS_GAME_FAVORITE_QUERY = """
+ SELECT EXISTS (SELECT 1 FROM games WHERE uid = $UID AND is_favorite = 1 LIMIT 1);
+ """;
+
+ private const string LIST_FAVORITE_GAMES_QUERY = """
+ SELECT uid FROM games WHERE is_favorite = 1;
+ """;
+
private Sqlite.Statement add_game_query;
private Sqlite.Statement add_game_uri_query;
private Sqlite.Statement update_game_query;
@@ -82,6 +95,10 @@ private class Games.Database : Object {
private Sqlite.Statement add_game_resource_query;
private Sqlite.Statement has_uri_query;
+ private Sqlite.Statement set_is_favorite_query;
+ private Sqlite.Statement is_game_favorite_query;
+ private Sqlite.Statement list_favorite_games_query;
+
public Database (string path) throws Error {
if (Sqlite.Database.open (path, out database) != Sqlite.OK)
throw new DatabaseError.COULDNT_OPEN ("Couldn’t open the database for “%s”.", path);
@@ -89,19 +106,35 @@ private class Games.Database : Object {
exec (CREATE_RESOURCES_TABLE_QUERY, null);
exec (CREATE_GAMES_TABLE_QUERY, null);
exec (CREATE_URIS_TABLE_QUERY, null);
+ }
+
+ public void prepare_statements () {
+ try {
+ add_game_query = prepare (database, ADD_GAME_QUERY);
+ add_game_uri_query = prepare (database, ADD_GAME_URI_QUERY);
+ update_game_query = prepare (database, UPDATE_GAME_QUERY);
+ delete_game_query = prepare (database, DELETE_GAME_QUERY);
+ delete_uri_query = prepare (database, DELETE_URI_QUERY);
- add_game_query = prepare (database, ADD_GAME_QUERY);
- add_game_uri_query = prepare (database, ADD_GAME_URI_QUERY);
- update_game_query = prepare (database, UPDATE_GAME_QUERY);
- delete_game_query = prepare (database, DELETE_GAME_QUERY);
- delete_uri_query = prepare (database, DELETE_URI_QUERY);
+ find_game_uris_query = prepare (database, FIND_GAME_URIS_QUERY);
+ get_cached_game_query = prepare (database, GET_CACHED_GAME_QUERY);
+ list_cached_games_query = prepare (database, LIST_CACHED_GAMES_QUERY);
- find_game_uris_query = prepare (database, FIND_GAME_URIS_QUERY);
- get_cached_game_query = prepare (database, GET_CACHED_GAME_QUERY);
- list_cached_games_query = prepare (database, LIST_CACHED_GAMES_QUERY);
+ add_game_resource_query = prepare (database, ADD_GAME_RESOURCE_QUERY);
+ has_uri_query = prepare (database, HAS_URI_QUERY);
- add_game_resource_query = prepare (database, ADD_GAME_RESOURCE_QUERY);
- has_uri_query = prepare (database, HAS_URI_QUERY);
+ set_is_favorite_query = prepare (database, SET_IS_FAVORITE_QUERY);
+ is_game_favorite_query = prepare (database, IS_GAME_FAVORITE_QUERY);
+ list_favorite_games_query = prepare (database, LIST_FAVORITE_GAMES_QUERY);
+ }
+ catch (Error e) {
+ assert_not_reached ();
+ }
+ }
+
+ public void apply_favorites_migration () throws Error {
+ info ("Applying database migration to support favorites");
+ exec ("ALTER TABLE games ADD COLUMN is_favorite INTEGER NOT NULL DEFAULT 0;", null);
}
public void add_uri (Uri uri) throws Error {
@@ -159,6 +192,17 @@ private class Games.Database : Object {
statement.bind_null (position);
}
+ internal static void bind_int (Sqlite.Statement statement, string parameter, int? integer) throws
Error {
+ var position = statement.bind_parameter_index (parameter);
+ if (position <= 0)
+ throw new DatabaseError.BINDING_FAILED ("Couldn't bind text to the parameter “%s”,
unexpected position: %d.", parameter, position);
+
+ if (integer != null)
+ statement.bind_int (position, integer);
+ else
+ statement.bind_null (position);
+ }
+
private string? serialize_media_set (Game game) {
var media_set = game.media_set;
@@ -290,8 +334,9 @@ private class Games.Database : Object {
var title = get_cached_game_query.column_text (1);
var platform = get_cached_game_query.column_text (2);
var media_set = get_cached_game_query.column_text (3);
+ var is_favorite = get_cached_game_query.column_int (4);
- return create_game (uid, uri, title, platform, media_set);
+ return create_game (uid, uri, title, platform, media_set, is_favorite);
}
throw new DatabaseError.EXECUTION_FAILED ("Couldn't get game for uid (%s)", uid);
@@ -306,13 +351,14 @@ private class Games.Database : Object {
var title = list_cached_games_query.column_text (2);
var platform = list_cached_games_query.column_text (3);
var media_set = list_cached_games_query.column_text (4);
+ var is_favorite = list_cached_games_query.column_int (5);
- var game = create_game (uid, uri, title, platform, media_set);
+ var game = create_game (uid, uri, title, platform, media_set, is_favorite);
game_callback (game);
}
}
- private Game create_game (string uid, string uri, string title, string platform, string? media_set) {
+ private Game create_game (string uid, string uri, string title, string platform, string? media_set,
int is_favorite) {
var game_uid = new Uid (uid);
var game_uri = new Uri (uri);
var game_title = new GenericTitle (title);
@@ -322,10 +368,50 @@ private class Games.Database : Object {
game_platform = new DummyPlatform ();
var game = new Game (game_uid, game_uri, game_title, game_platform);
+ game.is_favorite = is_favorite == 1;
if (media_set != null)
game.media_set = new MediaSet.parse (new Variant.parsed (media_set));
return game;
}
+
+ public bool set_is_favorite (Game game) throws Error {
+ if (game.is_favorite == is_game_favorite (game))
+ return false;
+
+ set_is_favorite_query.reset ();
+ bind_int (set_is_favorite_query, "$IS_FAVORITE", game.is_favorite ? 1 : 0);
+ bind_text (set_is_favorite_query, "$UID", game.uid.to_string ());
+
+ var result = set_is_favorite_query.step ();
+ if (result != Sqlite.DONE)
+ throw new DatabaseError.EXECUTION_FAILED ("Failed to make %s %sfavorite",
+ game.name, game.is_favorite ? "" : "non-");
+
+ return true;
+ }
+
+ public string[] list_favorite_games () throws Error {
+ list_favorite_games_query.reset ();
+ string[] games = {};
+
+ while (list_favorite_games_query.step () == Sqlite.ROW)
+ games += list_favorite_games_query.column_text (0);
+
+ return games;
+ }
+
+ private bool is_game_favorite (Game game) throws Error {
+ var uid = game.uid.to_string ();
+ is_game_favorite_query.reset ();
+ bind_text (is_game_favorite_query, "$UID", uid);
+
+ switch (is_game_favorite_query.step ()) {
+ case Sqlite.ROW:
+ return is_game_favorite_query.column_int (0) == 1;
+ default:
+ throw new DatabaseError.EXECUTION_FAILED ("Execution failed.");
+ }
+ }
}
diff --git a/src/ui/application.vala b/src/ui/application.vala
index 3e4b732f..5243d28c 100644
--- a/src/ui/application.vala
+++ b/src/ui/application.vala
@@ -133,9 +133,7 @@ public class Games.Application : Gtk.Application {
return;
data_dir.make_directory_with_parents ();
-
- var version_file = data_dir.get_child (".version");
- version_file.create (FileCreateFlags.NONE);
+ Migrator.bump_to_latest_version ();
}
catch (Error e) {
critical ("Couldn't create data dir: %s", e.message);
@@ -331,6 +329,12 @@ public class Games.Application : Gtk.Application {
if (game_collection != null)
return;
+ // Re-organize data_dir layout if necessary
+ // This operation has to be executed _after_ the PlatformsRegister has
+ // been populated and therefore this call is placed here
+ Migrator.apply_migration_if_necessary (database);
+ database.prepare_statements ();
+
TrackerUriSource tracker_uri_source = null;
try {
var connection = Tracker.Sparql.Connection.@get ();
@@ -407,11 +411,6 @@ public class Games.Application : Gtk.Application {
debug ("Error: %s", e.message);
}
}
-
- // Re-organize data_dir layout if necessary
- // This operation has to be executed _after_ the PlatformsRegister has
- // been populated and therefore this call is placed here
- Migrator.apply_migration_if_necessary ();
}
private Game? game_for_uris (Uri[] uris) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]