1/* 2 * Copyright (c) 2013-2016 gnome-pomodoro contributors 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 * Authors: Kamil Prusko <kamilprusko@gmail.com> 18 * 19 */ 20 21using GLib; 22 23 24namespace Pomodoro 25{ 26 public interface ApplicationExtension : Peas.ExtensionBase 27 { 28 } 29 30 internal Gom.Repository get_repository () 31 { 32 var application = Pomodoro.Application.get_default (); 33 34 return (Gom.Repository) application.get_repository (); 35 } 36 37 public class Application : Gtk.Application 38 { 39 private const uint REPOSITORY_VERSION = 1; 40 private const uint SETUP_PLUGINS_TIMEOUT = 3000; 41 42 public Pomodoro.Service service; 43 public Pomodoro.Timer timer; 44 public Pomodoro.CapabilityManager capabilities; 45 46 private Gom.Repository repository; 47 private Gom.Adapter adapter; 48 49 public GLib.Object get_repository () 50 { 51 return (GLib.Object) this.repository; 52 } 53 54 private Pomodoro.PreferencesDialog preferences_dialog; 55 private Pomodoro.Window window; 56 private Pomodoro.DesktopExtension desktop_extension; 57 private Gtk.Window about_dialog; 58 private Peas.ExtensionSet extensions; 59 private GLib.Settings settings; 60 private bool was_activated = false; 61 62 private enum ExitStatus 63 { 64 UNDEFINED = -1, 65 SUCCESS = 0, 66 FAILURE = 1 67 } 68 69 private class Options 70 { 71 public static bool no_default_window = false; 72 public static bool preferences = false; 73 public static bool quit = false; 74 public static bool start_stop = false; 75 public static bool start = false; 76 public static bool stop = false; 77 public static bool pause_resume = false; 78 public static bool pause = false; 79 public static bool resume = false; 80 81 public static ExitStatus exit_status = ExitStatus.UNDEFINED; 82 83 public const GLib.OptionEntry[] ENTRIES = { 84 { "start-stop", 0, 0, GLib.OptionArg.NONE, 85 ref start_stop, N_("Start/Stop"), null }, 86 87 { "start", 0, 0, GLib.OptionArg.NONE, 88 ref start, N_("Start"), null }, 89 90 { "stop", 0, 0, GLib.OptionArg.NONE, 91 ref stop, N_("Stop"), null }, 92 93 { "pause-resume", 0, 0, GLib.OptionArg.NONE, 94 ref pause_resume, N_("Pause/Resume"), null }, 95 96 { "pause", 0, 0, GLib.OptionArg.NONE, 97 ref pause, N_("Pause"), null }, 98 99 { "resume", 0, 0, GLib.OptionArg.NONE, 100 ref resume, N_("Resume"), null }, 101 102 { "no-default-window", 0, 0, GLib.OptionArg.NONE, 103 ref no_default_window, N_("Run as background service"), null }, 104 105 { "preferences", 0, 0, GLib.OptionArg.NONE, 106 ref preferences, N_("Show preferences"), null }, 107 108 { "quit", 0, 0, GLib.OptionArg.NONE, 109 ref quit, N_("Quit application"), null }, 110 111 { "version", 0, GLib.OptionFlags.NO_ARG, GLib.OptionArg.CALLBACK, 112 (void *) command_line_version_callback, N_("Print version information and exit"), null }, 113 114 { null } 115 }; 116 117 public static void reset () 118 { 119 Options.no_default_window = false; 120 Options.preferences = false; 121 Options.quit = false; 122 Options.start_stop = false; 123 Options.start = false; 124 Options.stop = false; 125 Options.pause_resume = false; 126 Options.pause = false; 127 Options.resume = false; 128 } 129 } 130 131 public Application () 132 { 133 GLib.Object ( 134 application_id: "org.gnome.Pomodoro", 135 flags: GLib.ApplicationFlags.HANDLES_COMMAND_LINE 136 ); 137 138 this.timer = null; 139 this.service = null; 140 } 141 142 public new static unowned Application get_default () 143 { 144 return GLib.Application.get_default () as Pomodoro.Application; 145 } 146 147 public unowned Gtk.Window get_last_focused_window () 148 { 149 unowned List<Gtk.Window> windows = this.get_windows (); 150 151 return windows != null 152 ? windows.first ().data 153 : null; 154 } 155 156 private void setup_resources () 157 { 158 var css_provider = new Gtk.CssProvider (); 159 css_provider.load_from_resource ("/org/gnome/pomodoro/style.css"); 160 161 Gtk.StyleContext.add_provider_for_screen ( 162 Gdk.Screen.get_default (), 163 css_provider, 164 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); 165 } 166 167 private void setup_desktop_extension () 168 { 169 try { 170 this.desktop_extension = new Pomodoro.DesktopExtension (); 171 172 this.capabilities.add_group (this.desktop_extension.capabilities, Pomodoro.Priority.HIGH); 173 } 174 catch (GLib.Error error) { 175 GLib.warning ("Error while initializing desktop extension: %s", 176 error.message); 177 } 178 } 179 180 private async void setup_plugins () 181 { 182 var engine = Peas.Engine.get_default (); 183 engine.add_search_path (Config.PLUGIN_LIB_DIR, Config.PLUGIN_DATA_DIR); 184 185 var timeout_cancellable = new GLib.Cancellable (); 186 var timeout_source = (uint) 0; 187 var wait_count = 0; 188 189 timeout_source = GLib.Timeout.add (SETUP_PLUGINS_TIMEOUT, () => { 190 GLib.debug ("Timeout reached while setting up plugins"); 191 192 timeout_source = 0; 193 timeout_cancellable.cancel (); 194 195 return GLib.Source.REMOVE; 196 }); 197 198 this.extensions = new Peas.ExtensionSet (engine, typeof (Pomodoro.ApplicationExtension)); 199 this.extensions.extension_added.connect ((extension_set, 200 info, 201 extension_object) => { 202 var extension = extension_object as GLib.AsyncInitable; 203 204 if (extension != null) 205 { 206 extension.init_async.begin (GLib.Priority.DEFAULT, timeout_cancellable, (obj, res) => { 207 try { 208 extension.init_async.end (res); 209 } 210 catch (GLib.Error error) { 211 GLib.warning ("Failed to initialize plugin \"%s\": %s", 212 info.get_module_name (), 213 error.message); 214 } 215 216 wait_count--; 217 218 this.setup_plugins.callback (); 219 }); 220 221 wait_count++; 222 } 223 }); 224 225 this.load_plugins (); 226 227 while (wait_count > 0) { 228 yield; 229 } 230 231 timeout_cancellable = null; 232 233 if (timeout_source != 0) { 234 GLib.Source.remove (timeout_source); 235 } 236 } 237 238 private void setup_capabilities () 239 { 240 var default_capabilities = new Pomodoro.CapabilityGroup ("default"); 241 242 default_capabilities.add (new Pomodoro.NotificationsCapability ("notifications")); 243 244 this.capabilities = new Pomodoro.CapabilityManager (); 245 this.capabilities.add_group (default_capabilities, Pomodoro.Priority.LOW); 246 } 247 248 private static bool migrate_repository (Gom.Repository repository, 249 Gom.Adapter adapter, 250 uint version) 251 throws GLib.Error 252 { 253 uint8[] file_contents; 254 string error_message; 255 256 GLib.debug ("Migrating database to version %u", version); 257 258 var file = File.new_for_uri ("resource:///org/gnome/pomodoro/database/version-%u.sql".printf (version)); 259 file.load_contents (null, out file_contents, null); 260 261 /* Gom.Adapter.execute_sql is limited to single line queries, 262 * so we need to use Sqlite API directly 263 */ 264 unowned Sqlite.Database database = adapter.get_handle (); 265 266 if (database.exec ((string) file_contents, null, out error_message) != Sqlite.OK) 267 { 268 throw new Gom.Error.COMMAND_SQLITE (error_message); 269 } 270 271 return true; 272 } 273 274 private void setup_repository () 275 { 276 this.hold (); 277 this.mark_busy (); 278 279 var path = GLib.Path.build_filename (GLib.Environment.get_user_data_dir (), 280 Config.PACKAGE_NAME, 281 "database.sqlite"); 282 var file = GLib.File.new_for_path (path); 283 var directory = file.get_parent (); 284 285 if (!directory.query_exists ()) { 286 try { 287 directory.make_directory_with_parents (); 288 } 289 catch (GLib.Error error) { 290 GLib.warning ("Failed to create directory: %s", error.message); 291 } 292 } 293 294 try { 295 /* Open database handle */ 296 var adapter = new Gom.Adapter (); 297 adapter.open_sync (file.get_uri ()); 298 this.adapter = adapter; 299 300 /* Migrate database if needed */ 301 var repository = new Gom.Repository (adapter); 302 repository.migrate_sync (Pomodoro.Application.REPOSITORY_VERSION, 303 Pomodoro.Application.migrate_repository); 304 305 this.repository = repository; 306 } 307 catch (GLib.Error error) { 308 GLib.critical ("Failed to migrate database: %s", error.message); 309 } 310 311 this.unmark_busy (); 312 this.release (); 313 } 314 315 private void load_plugins () 316 { 317 var engine = Peas.Engine.get_default (); 318 var enabled_plugins = this.settings.get_strv ("enabled-plugins"); 319 var enabled_hash = new GLib.HashTable<string, bool> (str_hash, str_equal); 320 321 foreach (var name in enabled_plugins) 322 { 323 enabled_hash.insert (name, true); 324 } 325 326 foreach (var plugin_info in engine.get_plugin_list ()) 327 { 328 if (plugin_info.is_hidden () || enabled_hash.contains (plugin_info.get_module_name ())) { 329 engine.try_load_plugin (plugin_info); 330 } 331 else { 332 engine.try_unload_plugin (plugin_info); 333 } 334 } 335 } 336 337 public void show_window (string mode, 338 uint32 timestamp = 0) 339 { 340 if (this.window == null) { 341 this.window = new Pomodoro.Window (); 342 this.window.application = this; 343 this.window.destroy.connect (() => { 344 this.remove_window (this.window); 345 this.window = null; 346 }); 347 348 this.add_window (this.window); 349 } 350 351 this.window.mode = mode != null && mode != "default" 352 ? mode : this.window.default_mode; 353 354 if (timestamp > 0) { 355 this.window.present_with_time (timestamp); 356 } 357 else { 358 this.window.present (); 359 } 360 } 361 362 public void show_preferences (uint32 timestamp = 0) 363 { 364 if (this.preferences_dialog == null) { 365 this.preferences_dialog = new Pomodoro.PreferencesDialog (); 366 this.preferences_dialog.destroy.connect (() => { 367 this.remove_window (this.preferences_dialog); 368 this.preferences_dialog = null; 369 }); 370 this.add_window (this.preferences_dialog); 371 } 372 373 if (this.preferences_dialog != null) { 374 if (timestamp > 0) { 375 this.preferences_dialog.present_with_time (timestamp); 376 } 377 else { 378 this.preferences_dialog.present (); 379 } 380 } 381 } 382 383 private void activate_timer (GLib.SimpleAction action, 384 GLib.Variant? parameter) 385 { 386 this.show_window ("timer"); 387 } 388 389 private void activate_stats (GLib.SimpleAction action, 390 GLib.Variant? parameter) 391 { 392 this.show_window ("stats"); 393 } 394 395 private void activate_preferences (GLib.SimpleAction action, 396 GLib.Variant? parameter) 397 { 398 this.show_preferences (); 399 } 400 401 private void activate_visit_website (GLib.SimpleAction action, 402 GLib.Variant? parameter) 403 { 404 try { 405 string[] spawn_args = { "xdg-open", Config.PACKAGE_URL }; 406 string[] spawn_env = GLib.Environ.get (); 407 408 GLib.Process.spawn_async (null, 409 spawn_args, 410 spawn_env, 411 GLib.SpawnFlags.SEARCH_PATH, 412 null, 413 null); 414 } 415 catch (GLib.SpawnError error) { 416 GLib.warning ("Failed to spawn process: %s", error.message); 417 } 418 } 419 420 private void activate_report_issue (GLib.SimpleAction action, 421 GLib.Variant? parameter) 422 { 423 try { 424 string[] spawn_args = { "xdg-open", Config.PACKAGE_BUGREPORT }; 425 string[] spawn_env = GLib.Environ.get (); 426 427 GLib.Process.spawn_async (null, 428 spawn_args, 429 spawn_env, 430 GLib.SpawnFlags.SEARCH_PATH, 431 null, 432 null); 433 } 434 catch (GLib.SpawnError error) { 435 GLib.warning ("Failed to spawn process: %s", error.message); 436 } 437 } 438 439 private void activate_about (GLib.SimpleAction action, 440 GLib.Variant? parameter) 441 { 442 if (this.about_dialog == null) 443 { 444 var window = this.get_last_focused_window (); 445 446 this.about_dialog = new Pomodoro.AboutDialog (); 447 this.about_dialog.destroy.connect (() => { 448 this.remove_window (this.about_dialog); 449 this.about_dialog = null; 450 }); 451 452 if (window != null) { 453 this.about_dialog.set_transient_for (window); 454 } 455 456 this.add_window (this.about_dialog); 457 } 458 459 this.about_dialog.present (); 460 } 461 462 private void activate_quit (GLib.SimpleAction action, 463 GLib.Variant? parameter) 464 { 465 this.quit (); 466 } 467 468 private void activate_timer_skip (GLib.SimpleAction action, 469 GLib.Variant? parameter) 470 { 471 try { 472 this.service.skip (); 473 } 474 catch (GLib.Error error) { 475 } 476 } 477 478 private void activate_timer_set_state (GLib.SimpleAction action, 479 GLib.Variant? parameter) 480 { 481 try { 482 this.service.set_state (parameter.get_string (), 0.0); 483 } 484 catch (GLib.Error error) { 485 } 486 } 487 488 private void activate_timer_switch_state (GLib.SimpleAction action, 489 GLib.Variant? parameter) 490 { 491 try { 492 this.service.set_state (parameter.get_string (), 493 this.timer.state.timestamp); 494 } 495 catch (GLib.Error error) { 496 } 497 } 498 499 private void setup_actions () 500 { 501 GLib.SimpleAction action; 502 503 action = new GLib.SimpleAction ("timer", null); 504 action.activate.connect (this.activate_timer); 505 this.add_action (action); 506 507 action = new GLib.SimpleAction ("stats", null); 508 action.activate.connect (this.activate_stats); 509 this.add_action (action); 510 511 action = new GLib.SimpleAction ("preferences", null); 512 action.activate.connect (this.activate_preferences); 513 this.add_action (action); 514 515 action = new GLib.SimpleAction ("visit-website", null); 516 action.activate.connect (this.activate_visit_website); 517 this.add_action (action); 518 519 action = new GLib.SimpleAction ("report-issue", null); 520 action.activate.connect (this.activate_report_issue); 521 this.add_action (action); 522 523 action = new GLib.SimpleAction ("about", null); 524 action.activate.connect (this.activate_about); 525 this.add_action (action); 526 527 action = new GLib.SimpleAction ("quit", null); 528 action.activate.connect (this.activate_quit); 529 this.add_action (action); 530 531 action = new GLib.SimpleAction ("timer-skip", null); 532 action.activate.connect (this.activate_timer_skip); 533 this.add_action (action); 534 535 action = new GLib.SimpleAction ("timer-set-state", GLib.VariantType.STRING); 536 action.activate.connect (this.activate_timer_set_state); 537 this.add_action (action); 538 539 action = new GLib.SimpleAction ("timer-switch-state", GLib.VariantType.STRING); 540 action.activate.connect (this.activate_timer_switch_state); 541 this.add_action (action); 542 543 this.set_accels_for_action ("stats.previous", {"<Alt>Left", "Back"}); 544 this.set_accels_for_action ("stats.next", {"<Alt>Right", "Forward"}); 545 this.set_accels_for_action ("app.quit", {"<Primary>q"}); 546 } 547 548 private static bool command_line_version_callback () 549 { 550 stdout.printf ("%s %s\n", 551 GLib.Environment.get_application_name (), 552 Config.PACKAGE_VERSION); 553 554 Options.exit_status = ExitStatus.SUCCESS; 555 556 return true; 557 } 558 559 /** 560 * Emitted on the primary instance immediately after registration. 561 */ 562 public override void startup () 563 { 564 this.hold (); 565 566 base.startup (); 567 568 this.restore_timer (); 569 570 this.setup_resources (); 571 this.setup_actions (); 572 this.setup_repository (); 573 this.setup_capabilities (); 574 this.setup_desktop_extension (); 575 this.setup_plugins.begin ((obj, res) => { 576 this.setup_plugins.end (res); 577 578 GLib.Idle.add (() => { 579 // TODO: shouldn't these be enabled by settings?! 580 this.capabilities.enable ("notifications"); 581 this.capabilities.enable ("indicator"); 582 this.capabilities.enable ("accelerator"); 583 this.capabilities.enable ("hide-system-notifications"); 584 this.capabilities.enable ("idle-monitor"); 585 586 this.release (); 587 588 return GLib.Source.REMOVE; 589 }); 590 }); 591 } 592 593 /** 594 * This is just for local things, like showing help 595 */ 596 private void parse_command_line (ref unowned string[] arguments) throws GLib.OptionError 597 { 598 var option_context = new GLib.OptionContext (); 599 option_context.add_main_entries (Options.ENTRIES, Config.GETTEXT_PACKAGE); 600 option_context.add_group (Gtk.get_option_group (true)); 601 602 // TODO: add options from plugins 603 604 option_context.parse (ref arguments); 605 } 606 607 protected override bool local_command_line ([CCode (array_length = false, array_null_terminated = true)] 608 ref unowned string[] arguments, 609 out int exit_status) 610 { 611 string[] tmp = arguments; 612 unowned string[] arguments_copy = tmp; 613 614 try 615 { 616 // This is just for local things, like showing help 617 this.parse_command_line (ref arguments_copy); 618 } 619 catch (GLib.Error error) 620 { 621 stderr.printf ("Failed to parse options: %s\n", error.message); 622 exit_status = ExitStatus.FAILURE; 623 624 return true; 625 } 626 627 if (Options.exit_status != ExitStatus.UNDEFINED) 628 { 629 exit_status = Options.exit_status; 630 631 return true; 632 } 633 634 return base.local_command_line (ref arguments, out exit_status); 635 } 636 637 public override int command_line (GLib.ApplicationCommandLine command_line) 638 { 639 string[] tmp = command_line.get_arguments (); 640 unowned string[] arguments_copy = tmp; 641 642 var exit_status = ExitStatus.SUCCESS; 643 644 do { 645 try 646 { 647 this.parse_command_line (ref arguments_copy); 648 } 649 catch (GLib.Error error) 650 { 651 stderr.printf ("Failed to parse options: %s\n", error.message); 652 653 exit_status = ExitStatus.FAILURE; 654 break; 655 } 656 657 if (Options.exit_status != ExitStatus.UNDEFINED) 658 { 659 exit_status = Options.exit_status; 660 break; 661 } 662 663 this.activate (); 664 } 665 while (false); 666 667 return exit_status; 668 } 669 670 /* Save the state before exit. 671 * 672 * Emitted only on the registered primary instance immediately after 673 * the main loop terminates. 674 */ 675 public override void shutdown () 676 { 677 this.hold (); 678 679 this.save_timer (); 680 681 foreach (var window in this.get_windows ()) { 682 this.remove_window (window); 683 } 684 685 this.capabilities.disable_all (); 686 687 var engine = Peas.Engine.get_default (); 688 689 foreach (var plugin_info in engine.get_plugin_list ()) { 690 engine.try_unload_plugin (plugin_info); 691 } 692 693 try { 694 if (this.adapter != null) { 695 this.adapter.close_sync (); 696 } 697 } 698 catch (GLib.Error error) { 699 } 700 701 base.shutdown (); 702 703 this.release (); 704 } 705 706 /* Emitted on the primary instance when an activation occurs. 707 * The application must be registered before calling this function. 708 */ 709 public override void activate () 710 { 711 this.hold (); 712 713 if (this.was_activated) { 714 Options.no_default_window |= Options.start_stop | 715 Options.start | 716 Options.stop | 717 Options.pause_resume | 718 Options.pause | 719 Options.resume; 720 } 721 722 if (Options.quit) { 723 this.quit (); 724 } 725 else { 726 if (Options.start_stop) { 727 this.timer.toggle (); 728 } 729 else if (Options.start) { 730 this.timer.start (); 731 } 732 else if (Options.stop) { 733 this.timer.stop (); 734 } 735 736 if (Options.pause_resume) { 737 if (this.timer.is_paused) { 738 this.timer.resume (); 739 } 740 else { 741 this.timer.pause (); 742 } 743 } 744 else if (Options.pause) { 745 this.timer.pause (); 746 } 747 else if (Options.resume) { 748 this.timer.resume (); 749 } 750 751 if (Options.preferences) { 752 this.show_preferences (); 753 } 754 else if (!Options.no_default_window) { 755 this.show_window ("default"); 756 } 757 758 Options.reset (); 759 } 760 761 this.was_activated = true; 762 763 this.release (); 764 } 765 766 public override bool dbus_register (GLib.DBusConnection connection, 767 string object_path) throws GLib.Error 768 { 769 if (!base.dbus_register (connection, object_path)) { 770 return false; 771 } 772 773 if (this.timer == null) { 774 this.timer = Pomodoro.Timer.get_default (); 775 this.timer.notify["is-paused"].connect (this.on_timer_is_paused_notify); 776 this.timer.state_changed.connect_after (this.on_timer_state_changed); 777 } 778 779 if (this.settings == null) { 780 this.settings = Pomodoro.get_settings () 781 .get_child ("preferences"); 782 this.settings.changed.connect (this.on_settings_changed); 783 } 784 785 if (this.service == null) { 786 this.hold (); 787 this.service = new Pomodoro.Service (connection, this.timer); 788 789 try { 790 connection.register_object ("/org/gnome/Pomodoro", this.service); 791 } 792 catch (GLib.IOError error) { 793 GLib.warning ("%s", error.message); 794 return false; 795 } 796 } 797 798 return true; 799 } 800 801 public override void dbus_unregister (GLib.DBusConnection connection, 802 string object_path) 803 { 804 base.dbus_unregister (connection, object_path); 805 806 if (this.timer != null) { 807 this.timer.destroy (); 808 this.timer = null; 809 } 810 811 if (this.service != null) { 812 this.service = null; 813 814 this.release (); 815 } 816 } 817 818 private void save_timer () 819 { 820 var state_settings = Pomodoro.get_settings () 821 .get_child ("state"); 822 823 this.timer.save (state_settings); 824 } 825 826 private void restore_timer () 827 { 828 var state_settings = Pomodoro.get_settings () 829 .get_child ("state"); 830 831 this.timer.restore (state_settings); 832 } 833 834 private void on_settings_changed (GLib.Settings settings, 835 string key) 836 { 837 var state_duration = this.timer.state_duration; 838 839 switch (key) 840 { 841 case "pomodoro-duration": 842 if (this.timer.state is Pomodoro.PomodoroState) { 843 state_duration = settings.get_double (key); 844 } 845 break; 846 847 case "short-break-duration": 848 if (this.timer.state is Pomodoro.ShortBreakState) { 849 state_duration = settings.get_double (key); 850 } 851 break; 852 853 case "long-break-duration": 854 if (this.timer.state is Pomodoro.LongBreakState) { 855 state_duration = settings.get_double (key); 856 } 857 break; 858 859 case "enabled-plugins": 860 this.load_plugins (); 861 862 break; 863 } 864 865 if (state_duration != this.timer.state_duration) 866 { 867 this.timer.state_duration = double.max (state_duration, this.timer.elapsed); 868 } 869 } 870 871 private void on_timer_is_paused_notify () 872 { 873 this.save_timer (); 874 } 875 876 /** 877 * Save timer state, assume user is idle when break is completed. 878 */ 879 private void on_timer_state_changed (Pomodoro.Timer timer, 880 Pomodoro.TimerState state, 881 Pomodoro.TimerState previous_state) 882 { 883 this.hold (); 884 this.save_timer (); 885 886 if (this.timer.is_paused) 887 { 888 this.timer.resume (); 889 } 890 891 if (!(previous_state is Pomodoro.DisabledState)) 892 { 893 var entry = new Pomodoro.Entry.from_state (previous_state); 894 entry.repository = this.repository; 895 entry.save_async.begin ((obj, res) => { 896 try { 897 entry.save_async.end (res); 898 } 899 catch (GLib.Error error) { 900 GLib.warning ("Error while saving entry: %s", error.message); 901 } 902 903 this.release (); 904 }); 905 } 906 } 907 } 908} 909