diff --git a/src/crosswords-app.c b/src/crosswords-app.c index 8b629472566f4c1585fdcdc83c199f247282dbba..371116455ff5fa54ca8e9a0034ccec81e3dc407a 100644 --- a/src/crosswords-app.c +++ b/src/crosswords-app.c @@ -26,9 +26,11 @@ #include "crosswords-app.h" #include "crosswords-credits.h" #include "crosswords-init.h" +#include "crosswords-init-startup.h" #include "play-window.h" #include "puzzle-downloader.h" + struct _CrosswordsApp { AdwApplication parent; @@ -123,8 +125,8 @@ crosswords_app_startup (GApplication *app) G_APPLICATION_CLASS (crosswords_app_parent_class)->startup (app); - crosswords_init (); + crosswords_init_startup (); gtk_application_set_accels_for_action (GTK_APPLICATION (app), "app.quit", quit_accels); gtk_application_set_accels_for_action (GTK_APPLICATION (app), "app.open", open_accels); diff --git a/src/crosswords-init-startup.c b/src/crosswords-init-startup.c new file mode 100644 index 0000000000000000000000000000000000000000..4931d431c6861491bf724b8ef69e86528581bf3e --- /dev/null +++ b/src/crosswords-init-startup.c @@ -0,0 +1,99 @@ +/* crosswords-app-startup.c + * + * Copyright 2025 Mahmoud Abdelghany + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + + +#include "crosswords-config.h" +#include +#include +#include "crosswords-init-startup.h" +#include "puzzle-downloader.h" +#include "puzzle-set-list.h" +#include "puzzle-set.h" + + +static GSettings *global_settings = NULL; + + +static void +startup_auto_download (void) +{ + g_autoptr (GError) error = NULL; + gboolean auto_download; + GNetworkMonitor *monitor; + monitor = g_network_monitor_get_default (); + + global_settings = g_settings_new ("org.gnome.Crosswords"); + auto_download = g_settings_get_boolean (global_settings, "auto-download-puzzle-sets"); + + if (auto_download) + { + GStrv shown_puzzle_sets; + GListModel *puzzle_set_list; + guint n_items; + + if (!g_network_monitor_get_network_available (monitor)) + return; + + shown_puzzle_sets = g_settings_get_strv (global_settings, "shown-puzzle-sets"); + puzzle_set_list = puzzle_set_list_get (); + n_items = g_list_model_get_n_items (puzzle_set_list); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr (PuzzleSet) puzzle_set = NULL; + const gchar *id; + gboolean is_enabled = FALSE; + + puzzle_set = g_list_model_get_item (puzzle_set_list, i); + id = puzzle_set_get_id (puzzle_set); + + for (gint j = 0; shown_puzzle_sets[j] != NULL; j++) + { + if (g_strcmp0 (id, shown_puzzle_sets[j]) == 0) + { + is_enabled = TRUE; + break; + } + } + + if (is_enabled && puzzle_set_get_auto_download (puzzle_set)) + { + PuzzleDownloader *downloader; + GCancellable *downloader_cancellable; + + downloader = puzzle_set_get_downloader(puzzle_set); + downloader_cancellable = puzzle_set_get_downloader_cancellable(puzzle_set); + + if (downloader == NULL) + continue; + + puzzle_downloader_run_auto (downloader, downloader_cancellable, &error); + puzzle_set_set_downloader_state (puzzle_set, + DOWNLOADER_STATE_DOWNLOADING); + } + } + } +} + +void +crosswords_init_startup (void) +{ + startup_auto_download (); +} diff --git a/src/crosswords-init-startup.h b/src/crosswords-init-startup.h new file mode 100644 index 0000000000000000000000000000000000000000..26d9342d680f291921744deeba06e97e3a3df7b9 --- /dev/null +++ b/src/crosswords-init-startup.h @@ -0,0 +1,31 @@ +/* crosswords-app-startup.h + * + * Copyright 2025 Mahmoud Abdelghany + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +void crosswords_init_startup (void); + +G_END_DECLS diff --git a/src/crosswords-init.c b/src/crosswords-init.c index 9f924690eef3651dfa83d2702cf8f08b73025ae9..d7c229a97465f6dd460348dc0b73b23dbb9257e4 100644 --- a/src/crosswords-init.c +++ b/src/crosswords-init.c @@ -113,7 +113,7 @@ crosswords_init_environment (void) extend_env_var ("PATH", LIBEXECDIR); /* FIXME: see Issue #53 "downloader environments should be cleaned up for flatpaks" */ - if (crosswords_inside_flatpak ()) + if (crosswords_get_inside_flatpak ()) { GDir *extensions_dir; const gchar *name; @@ -167,8 +167,7 @@ crosswords_init (void) /** - * crosswords_inside_flatpak: - * @void: + * crosswords_get_inside_flatpak: * * Let's us know if we're currently running within a flatpak. While we * don't want to do anything dramatically different in that situation, @@ -179,7 +178,7 @@ crosswords_init (void) * Returns: %TRUE, if we're running within a flatpak **/ gboolean -crosswords_inside_flatpak (void) +crosswords_get_inside_flatpak (void) { static gsize guard = 0; diff --git a/src/crosswords-init.h b/src/crosswords-init.h index 8fe3bc954542428747651157c82a8575ef7b5a01..419916d8b1c89b35e6b7f63b77da3e2ddb7c1514 100644 --- a/src/crosswords-init.h +++ b/src/crosswords-init.h @@ -25,7 +25,9 @@ G_BEGIN_DECLS -void crosswords_init (void); -gboolean crosswords_inside_flatpak (void); + +void crosswords_init (void); +gboolean crosswords_get_inside_flatpak (void); + G_END_DECLS diff --git a/src/meson.build b/src/meson.build index 87ddbc5b6fd483ab114ce2b0f2c3f05c37a00479..75b1135382364ea4cadad35c4cde316bbbe6fa20 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,6 +6,7 @@ common_sources = files ( 'crosswords-app.c', 'crosswords-credits.c', 'crosswords-init.c', + 'crosswords-init-startup.c', 'crosswords-misc.c', 'crosswords-quirks.c', 'contrib/gnome-languages.c', @@ -44,6 +45,7 @@ common_headers = files ( 'crosswords-app.h', 'crosswords-credits.h', 'crosswords-init.h', + 'crosswords-init-startup.h', 'crosswords-misc.h', 'crosswords-quirks.h', 'contrib/gnome-languages.h', diff --git a/src/play-preferences-dialog.ui b/src/play-preferences-dialog.ui index 1e4cd2ef3d25d401df14f0a9ea531e6d04100ee3..1241e5d2b3bbe06b450630f19e60b7c4c3aac6cd 100644 --- a/src/play-preferences-dialog.ui +++ b/src/play-preferences-dialog.ui @@ -90,7 +90,7 @@ Auto Download - false + true Automatically Download Latest Puzzles On Startup diff --git a/src/puzzle-downloader.c b/src/puzzle-downloader.c index af0e8704a65c9edbe4bbefc92f27b1dd5f66b260..2cd57416af1d9d8070a3d82cdac2401d831287ba 100644 --- a/src/puzzle-downloader.c +++ b/src/puzzle-downloader.c @@ -25,9 +25,11 @@ #include "puzzle-downloader.h" #include "puzzle-downloader-dialog.h" + #define MASTER_SCHEMA "org.gnome.Crosswords.puzzle-sets" #define PUZZLE_SET_PATH_PREFIX "/org/gnome/Crosswords/puzzle-sets/" + enum { FINISHED, N_SIGNALS @@ -99,7 +101,9 @@ static const gchar *format_name[] = { static void puzzle_downloader_init (PuzzleDownloader *self); static void puzzle_downloader_class_init (PuzzleDownloaderClass *klass); static void puzzle_downloader_dispose (GObject *object); -static void downloader_cancel_timeout_add (PuzzleDownloader *downloader); +static void downloader_cancel_timeout_add (PuzzleDownloader *downloader, + guint timeout_ms, + GSourceFunc timeout_cb); static void downloader_cancel_timeout_remove (PuzzleDownloader *downloader); static void downloader_show_error_dialog (PuzzleDownloader *downloader, const gchar *secondary_text); @@ -260,14 +264,31 @@ downloader_cancel_timeout_cb (PuzzleDownloader *downloader) return G_SOURCE_REMOVE; } +static gboolean +auto_downloader_cancel_timeout_cb (PuzzleDownloader *downloader) +{ + g_assert (PUZZLE_IS_DOWNLOADER (downloader)); + g_debug ("Timeout reached for auto-download with header: %s, cancelling subprocess", downloader->header); + if (downloader->temp_file) + { + unlink (downloader->temp_file); + g_clear_pointer (&downloader->temp_file, g_free); + } + if (downloader->subprocess) + g_subprocess_force_exit (downloader->subprocess); + downloader->cancel_timeout = 0; + g_signal_emit (downloader, obj_signals[FINISHED], 0, NULL, FALSE); + + return G_SOURCE_REMOVE; +} + static void -downloader_cancel_timeout_add (PuzzleDownloader *downloader) +downloader_cancel_timeout_add (PuzzleDownloader *downloader, + guint timeout_ms, + GSourceFunc timeout_cb) { g_assert (downloader->cancel_timeout == 0); - - downloader->cancel_timeout = g_timeout_add (2000, - (GSourceFunc) downloader_cancel_timeout_cb, - downloader); + downloader->cancel_timeout = g_timeout_add (timeout_ms, timeout_cb, downloader); } static void @@ -303,6 +324,7 @@ downloader_show_error_dialog (PuzzleDownloader *downloader, */ + void convertor_finished_cb (GSubprocess *subprocess, GAsyncResult *res, @@ -464,7 +486,7 @@ command_finished_cb (GSubprocess *subprocess, downloader_cancel_timeout_remove (downloader); g_clear_pointer (&downloader->cancel_dialog, adw_dialog_close); g_clear_object (&downloader->subprocess); - + /* If we successfully run the subcommand and captured it's stdout, * we either load the file, or potentially convert it. */ if (temp_file) @@ -492,6 +514,41 @@ command_finished_cb (GSubprocess *subprocess, } } +static void +auto_command_finished_cb (GSubprocess *subprocess, + GAsyncResult *res, + PuzzleDownloader *downloader) +{ + gchar* temp_file = NULL; + + downloader_cancel_timeout_remove (downloader); + + if (g_subprocess_get_successful (subprocess)) + temp_file = stdout_to_temp_file (subprocess); + + if (temp_file) + { + if (downloader->format == DOWNLOADER_FORMAT_IPUZ) + { + g_autofree gchar* uri = NULL; + + uri = g_filename_to_uri(temp_file, NULL, NULL); + g_signal_emit(downloader, obj_signals[FINISHED], 0, uri, TRUE); + g_free(temp_file); + } + else + { + g_assert(downloader->temp_file == NULL); + downloader->temp_file = temp_file; + convert_puz_to_ipuz(downloader); + } + } + else + { + g_signal_emit(downloader, obj_signals[FINISHED], 0, NULL, FALSE); + } +} + static gchar ** generate_argv (PuzzleDownloader *downloader) { @@ -537,9 +594,12 @@ generate_argv (PuzzleDownloader *downloader) static void -run_command (PuzzleDownloader *downloader, - GCancellable *cancellable, - GError **error) +run_command (PuzzleDownloader *downloader, + GCancellable *cancellable, + GError **error, + GAsyncReadyCallback finished_cb, + guint timeout_ms, + GSourceFunc timeout_cb) { g_autoptr (GError) tmp_error = NULL; gchar **argv; @@ -568,12 +628,12 @@ run_command (PuzzleDownloader *downloader, return; } - downloader_cancel_timeout_add (downloader); + downloader_cancel_timeout_add (downloader, timeout_ms, timeout_cb); downloader->wait_cancellable = g_cancellable_new (); g_subprocess_wait_async (downloader->subprocess, downloader->wait_cancellable, - (GAsyncReadyCallback) command_finished_cb, + finished_cb, downloader); } @@ -659,7 +719,7 @@ start_file_import (PuzzleDownloader *downloader) if (downloader->format != DOWNLOADER_FORMAT_IPUZ) downloader->temp_file = g_file_get_path (destination); - downloader_cancel_timeout_add (downloader); + downloader_cancel_timeout_add (downloader, 2000, (GSourceFunc) downloader_cancel_timeout_cb); downloader->wait_cancellable = g_cancellable_new (); flags = G_FILE_COPY_OVERWRITE; @@ -773,7 +833,9 @@ on_input_dialog_response (PuzzleDownloaderDialog *dialog, else if (downloader->type == DOWNLOADER_TYPE_ENTRY) downloader->string_value = puzzle_downloader_dialog_get_entry (dialog); - run_command (downloader, NULL, NULL); + run_command (downloader, NULL, NULL, + (GAsyncReadyCallback) command_finished_cb, 2000, + (GSourceFunc) downloader_cancel_timeout_cb); } else { @@ -928,10 +990,27 @@ puzzle_downloader_run_async (PuzzleDownloader *downloader, } else if (downloader->type == DOWNLOADER_TYPE_AUTO) { - run_command (downloader, cancellable, error); + run_command (downloader, + cancellable, + error, + (GAsyncReadyCallback)command_finished_cb, + 2000, + (GSourceFunc) downloader_cancel_timeout_cb); } else { run_input_dialog (downloader); } } + +void +puzzle_downloader_run_auto(PuzzleDownloader *downloader, + GCancellable *cancellable, + GError **error) +{ + g_return_if_fail (PUZZLE_IS_DOWNLOADER (downloader)); + + run_command (downloader, cancellable, error, + (GAsyncReadyCallback)auto_command_finished_cb, 10000, + (GSourceFunc)auto_downloader_cancel_timeout_cb); +} diff --git a/src/puzzle-downloader.h b/src/puzzle-downloader.h index efbdf1ba17dd99d8704ef179ffe9bb48038b0f56..9f6f16db8c436e851e9d62020c535b8a37a4cbe1 100644 --- a/src/puzzle-downloader.h +++ b/src/puzzle-downloader.h @@ -60,7 +60,9 @@ void puzzle_downloader_run_async (PuzzleDownloader *dow GtkWindow *parent_window, GCancellable *cancellable, GError **error); - +void puzzle_downloader_run_auto (PuzzleDownloader *downloader, + GCancellable *cancellable, + GError **error); G_END_DECLS diff --git a/src/puzzle-set.c b/src/puzzle-set.c index c3cdb4aaedc20d9b08106525d4d9e0b492a0638f..a16e28d7de9517310170a200ce42bc117be97cf8 100644 --- a/src/puzzle-set.c +++ b/src/puzzle-set.c @@ -32,7 +32,7 @@ #include "puzzle-set.h" #include "puzzle-set-config.h" #include "puzzle-set-model.h" - +#include "puzzle-set-list.h" enum { PUZZLES_START, @@ -98,8 +98,6 @@ static void puzzle_set_real_change_phase (PuzzleSet *puzzle_set static void puzzle_set_real_puzzles_done (PuzzleSet *puzzle_set); static void puzzle_set_save (PuzzleSet *self); static void start_download_cb (PuzzleSet *self); -static void puzzle_set_set_downloader_state (PuzzleSet *self, - DownloaderState state); G_DEFINE_TYPE_WITH_PRIVATE (PuzzleSet, puzzle_set, G_TYPE_OBJECT); @@ -875,7 +873,7 @@ network_available_cb (GNetworkMonitor *monitor, } } -static void +void puzzle_set_set_downloader_state (PuzzleSet *self, DownloaderState state) { @@ -1039,3 +1037,18 @@ puzzle_set_new (GResource *resource) return puzzle_set; } + +/* FIXME(refactor): These downloader-related functions might not belong here. */ +PuzzleDownloader * +puzzle_set_get_downloader (PuzzleSet *puzzle_set) +{ + PuzzleSetPrivate *priv = puzzle_set_get_instance_private (puzzle_set); + return priv->downloader; +} + +GCancellable * +puzzle_set_get_downloader_cancellable (PuzzleSet *puzzle_set) +{ + PuzzleSetPrivate *priv = puzzle_set_get_instance_private (puzzle_set); + return priv->downloader_cancellable; +} \ No newline at end of file diff --git a/src/puzzle-set.h b/src/puzzle-set.h index df9d6c8b2d8f637af021a81cd14b71bcac740fa6..81cc3ea4e52776cd828f92ddde8a3e65a7f63ab0 100644 --- a/src/puzzle-set.h +++ b/src/puzzle-set.h @@ -23,7 +23,7 @@ #include #include - +#include "puzzle-downloader.h" #include "puzzle-set-config.h" @@ -59,44 +59,45 @@ struct _PuzzleSetClass void (* reveal_canceled) (PuzzleSet *puzzle_set); }; -PuzzleSet *puzzle_set_new (GResource *resource); -PuzzleSetType puzzle_set_get_puzzle_type (PuzzleSet *puzzle_set); -const gchar *puzzle_set_get_id (PuzzleSet *puzzle_set); -const gchar *puzzle_set_get_short_name (PuzzleSet *puzzle_set); -const gchar *puzzle_set_get_long_name (PuzzleSet *puzzle_set); -const gchar *puzzle_set_get_locale (PuzzleSet *puzzle_set); -const gchar *puzzle_set_get_language (PuzzleSet *puzzle_set); -gboolean puzzle_set_get_disabled (PuzzleSet *puzzle_set); -gboolean puzzle_set_get_shown (PuzzleSet *puzzle_set); -void puzzle_set_set_shown (PuzzleSet *puzzle_set, - gboolean shown); -gboolean puzzle_set_get_auto_download (PuzzleSet *puzzle_set); -ConfigSetTags puzzle_set_get_tags (PuzzleSet *puzzle_set); - - -void puzzle_set_puzzles_start (PuzzleSet *puzzle_set); -void puzzle_set_change_phase (PuzzleSet *puzzle_set, - PuzzlePhase phase); -void puzzle_set_puzzles_done (PuzzleSet *puzzle_set); -void puzzle_set_reveal_canceled (PuzzleSet *puzzle_set); - - -GtkWidget *puzzle_set_get_widget (PuzzleSet *puzzle_set, - PuzzlePhase phase); -IpuzPuzzle *puzzle_set_get_puzzle (PuzzleSet *puzzle_set, - PuzzlePhase phase); -const gchar *puzzle_set_get_title (PuzzleSet *puzzle_set, - PuzzlePhase phase); -void puzzle_set_reset_puzzle (PuzzleSet *puzzle_set); -void puzzle_set_reveal_toggled (PuzzleSet *puzzle_set, - gboolean reveal); -void puzzle_set_show_hint (PuzzleSet *puzzle_set); -const gchar *puzzle_set_get_uri (PuzzleSet *puzzle_set, - PuzzlePhase phase); -void puzzle_set_import_uri (PuzzleSet *puzzle_set, - const gchar *uri); -guint puzzle_set_get_n_puzzles (PuzzleSet *puzzle_set); -guint puzzle_set_get_n_won (PuzzleSet *puzzle_set); - +PuzzleSet *puzzle_set_new (GResource *resource); +PuzzleSetType puzzle_set_get_puzzle_type (PuzzleSet *puzzle_set); +const gchar *puzzle_set_get_id (PuzzleSet *puzzle_set); +const gchar *puzzle_set_get_short_name (PuzzleSet *puzzle_set); +const gchar *puzzle_set_get_long_name (PuzzleSet *puzzle_set); +const gchar *puzzle_set_get_locale (PuzzleSet *puzzle_set); +const gchar *puzzle_set_get_language (PuzzleSet *puzzle_set); +gboolean puzzle_set_get_disabled (PuzzleSet *puzzle_set); +gboolean puzzle_set_get_shown (PuzzleSet *puzzle_set); +void puzzle_set_set_shown (PuzzleSet *puzzle_set, + gboolean shown); +gboolean puzzle_set_get_auto_download (PuzzleSet *puzzle_set); +ConfigSetTags puzzle_set_get_tags (PuzzleSet *puzzle_set); + +void puzzle_set_puzzles_start (PuzzleSet *puzzle_set); +void puzzle_set_change_phase (PuzzleSet *puzzle_set, + PuzzlePhase phase); +void puzzle_set_puzzles_done (PuzzleSet *puzzle_set); +void puzzle_set_reveal_canceled (PuzzleSet *puzzle_set); + +GtkWidget *puzzle_set_get_widget (PuzzleSet *puzzle_set, + PuzzlePhase phase); +IpuzPuzzle *puzzle_set_get_puzzle (PuzzleSet *puzzle_set, + PuzzlePhase phase); +const gchar *puzzle_set_get_title (PuzzleSet *puzzle_set, + PuzzlePhase phase); +void puzzle_set_reset_puzzle (PuzzleSet *puzzle_set); +void puzzle_set_reveal_toggled (PuzzleSet *puzzle_set, + gboolean reveal); +void puzzle_set_show_hint (PuzzleSet *puzzle_set); +const gchar *puzzle_set_get_uri (PuzzleSet *puzzle_set, + PuzzlePhase phase); +void puzzle_set_import_uri (PuzzleSet *puzzle_set, + const gchar *uri); +guint puzzle_set_get_n_puzzles (PuzzleSet *puzzle_set); +guint puzzle_set_get_n_won (PuzzleSet *puzzle_set); +PuzzleDownloader *puzzle_set_get_downloader (PuzzleSet *puzzle_set); +GCancellable *puzzle_set_get_downloader_cancellable (PuzzleSet *puzzle_set); +void puzzle_set_set_downloader_state (PuzzleSet *puzzle_set, + DownloaderState state); G_END_DECLS