From 0b0bb7a7b543a90e7fc661a9006a44f2298ed206 Mon Sep 17 00:00:00 2001 From: Sergiotarxz Date: Mon, 4 Mar 2024 01:10:23 +0100 Subject: [PATCH] Adding a log window with restore save options. --- Build.PL | 1 + lib/GEmeTool/DB/Migrations.pm | 11 ++- lib/GEmeTool/Log.pm | 92 ++++++++++++++++++++++ lib/GEmeTool/Save.pm | 53 ++++++++++++- lib/GEmeTool/View/LogWindow.pm | 130 ++++++++++++++++++++++++++++++++ lib/GEmeTool/View/MainWindow.pm | 15 +++- 6 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 lib/GEmeTool/Log.pm create mode 100644 lib/GEmeTool/View/LogWindow.pm diff --git a/Build.PL b/Build.PL index 1e4e1e9..d45c98a 100755 --- a/Build.PL +++ b/Build.PL @@ -17,6 +17,7 @@ my $build = Module::Build->new( 'File::HomeDir' => 0, 'Moo' => 0, 'namespace::clean' => 0, + 'UUID::URandom' => 0, }, ); $build->create_build_script; diff --git a/lib/GEmeTool/DB/Migrations.pm b/lib/GEmeTool/DB/Migrations.pm index dc9882a..9709250 100644 --- a/lib/GEmeTool/DB/Migrations.pm +++ b/lib/GEmeTool/DB/Migrations.pm @@ -13,7 +13,16 @@ sub MIGRATIONS { 'CREATE TABLE options ( name TEXT PRIMARY KEY, value TEXT - )', + );', + 'CREATE TABLE logs ( + uuid TEXT PRIMARY KEY, + date datetime NOT NULL, + message TEXT NOT NULL, + input_file TEXT, + output_file TEXT, + backup_input_file TEXT, + backup_output_file TEXT + );', ); } 1; diff --git a/lib/GEmeTool/Log.pm b/lib/GEmeTool/Log.pm new file mode 100644 index 0000000..7a24e1f --- /dev/null +++ b/lib/GEmeTool/Log.pm @@ -0,0 +1,92 @@ +package GEmeTool::Log; + +use v5.16.3; + +use strict; +use warnings; + +use Moo; +use parent 'Exporter'; + +use Digest::SHA qw/sha256_hex/; + +use Path::Tiny; +use UUID::URandom qw/create_uuid_string/; + +use GEmeTool::DB; +use GEmeTool::Config; + +our @EXPORT_OK = qw/logger/; + +sub logger { + return GEmeTool::Log->new; +} + +sub msg { + my $self = shift; + my $msg = shift; + my $input_file = shift; + my $output_file = shift; + my ( $input_file_backup, $output_file_backup ) = + $self->backup_files( $input_file, $output_file ); + my $db = GEmeTool::DB->connect; + my $uuid = create_uuid_string(); + my $insert = <<'EOF'; +INSERT INTO logs + (uuid, date, message, input_file, + output_file, backup_input_file, + backup_output_file) +VALUES (?, datetime('now'), ?, ?, ?, ?, ?);'); +EOF + return $db->do( $insert, {}, $uuid, $msg, $input_file, $output_file, + $input_file_backup, $output_file_backup ); +} + +sub backup_files { + my $self = shift; + my @files = @_; + my $data_dir = GEmeTool::Config->new->data_dir; + my $backup_dir = $data_dir->child('backups'); + $backup_dir->mkpath; + @files = map { sub {return undef if !defined $_; return path($_)->absolute}->() } @files; + my @result_files; + for my $file (@files) { + if (!defined $file || !-f $file) { + push @result_files, undef; + next; + } + my $contents = $file->slurp_raw; + my $digest = sha256_hex($contents); + my ($possible_output) = + grep { $_->basename =~ /^$digest-/ } $backup_dir->children; + if ( defined $possible_output ) { + + # We should probably check that the backup + # is unmodified, but I cannot + # figure a user friendly way to recover from + # that error. + push @result_files, $possible_output; + next; + } + my $basename = $file->basename; + my $output_file = $backup_dir->child("$digest-$basename"); + $output_file->spew_raw($contents); + push @result_files, $output_file; + } + return @result_files; +} + +sub get_logs { + my $self = shift; + my $db = GEmeTool::DB->connect; + my $query = <<'EOF'; +SELECT message, date, input_file, + output_file, backup_input_file, + backup_output_file +FROM logs +ORDER BY date; +EOF + my $results = $db->selectall_arrayref($query, {Slice => {}}); + return $results; +} +1; diff --git a/lib/GEmeTool/Save.pm b/lib/GEmeTool/Save.pm index d4b3659..82ed046 100644 --- a/lib/GEmeTool/Save.pm +++ b/lib/GEmeTool/Save.pm @@ -14,6 +14,7 @@ use Rsaves::Constants::Emerald::Rematches; use Moo; +use GEmeTool::Log qw/logger/; use namespace::clean; has __saves => ( is => 'rw' ); @@ -22,6 +23,10 @@ has __extra => ( is => 'rw' ); has input_file => ( is => 'rw', required => 1 ); +has _initial_rematches => ( is => 'rw' ); + +has _initial_flags => ( is => 'rw' ); + sub instance { my $class = shift; my $input_file = shift; @@ -29,6 +34,11 @@ sub instance { return $save; } +sub BUILD { + my $self = shift; + logger()->msg( 'Opening save...', $self->input_file ); +} + sub get_flags_save { my $self = shift; my %flags_by_id = $self->_get_flags_hash_by_id; @@ -56,13 +66,41 @@ sub get_rematches_save { } sub save { - my $self = shift; - my $file = shift; - my $extra = $self->__extra; - my $saves = $self->__saves; + my $self = shift; + my $file = shift; + my $extra = $self->__extra; + my $saves = $self->__saves; + my $ini_flags = $self->_initial_flags; + my $end_flags = $self->get_flags_save; + my $ini_rematches = $self->_initial_rematches; + my $end_rematches = $self->get_rematches_save; + logger()->msg( + "Saving, edited data:\n" + . $self->diff_boolean_flag_like( $ini_flags, $end_flags ) + . $self->diff_boolean_flag_like( $ini_rematches, $end_rematches ), + $self->input_file, + $file, + ); Rsaves::save_changes( @$saves, $extra, $file ); } +sub diff_boolean_flag_like { + my $self = shift; + my $initial = shift; + my $final = shift; + my $result_string = ''; + for ( my $i = 0 ; $i < scalar @$initial ; $i++ ) { + my $flag_ini = $initial->[$i]; + my $flag_end = $final->[$i]; + if ( ( !!$flag_ini->{value} ) == ( !!$flag_end->{value} ) ) { + next; + } + $result_string .= + "@{[$flag_end->{name}]} => @{[$flag_end->{value} ? 'ON' : 'OFF']}\n"; + } + return $result_string; +} + sub has_rematch { my $self = shift; my $flag_id = shift; @@ -137,6 +175,13 @@ sub _build_saves_and_extra { my @saves = get_saves( @saves_raw, $EMERALD_VERSION ); $self->__saves( \@saves ); $self->__extra($extra); + $self->store_initial_flags_and_rematch; +} + +sub store_initial_flags_and_rematch { + my $self = shift; + $self->_initial_flags( [@{$self->get_flags_save}] ); + $self->_initial_rematches( [@{$self->get_rematches_save}] ); } sub _get_rematches_constant_list { diff --git a/lib/GEmeTool/View/LogWindow.pm b/lib/GEmeTool/View/LogWindow.pm new file mode 100644 index 0000000..b383dba --- /dev/null +++ b/lib/GEmeTool/View/LogWindow.pm @@ -0,0 +1,130 @@ +package GEmeTool::View::LogWindow; + +use v5.16.3; +use strict; +use warnings; +use utf8; + +use Glib::IO; +use Glib::Object::Introspection; +use Path::Tiny; + +use GEmeTool::Log qw/logger/; +use Moo; + +use namespace::clean; + +Glib::Object::Introspection->setup( + basename => 'Gtk', + version => '4.0', + package => 'Gtk4', +); + +Glib::Object::Introspection->setup( + basename => 'Gdk', + version => '4.0', + package => 'Gdk', +); + +use namespace::clean; + +has app => ( + is => 'ro', + required => 1, +); + +has _win => ( is => 'rw', ); + +sub start { + my $self = shift; + $self->_win( Gtk4::Window->new ); + my $win = $self->_win; + $win->set_title('GEmeTool Log Viewer'); + $win->set_default_size( 600, 600 ); + $self->fill_logs_win; + $win->present; +} + +sub fill_logs_win { + my $self = shift; + my $win = $self->_win; + my $logs = GEmeTool::Log->new->get_logs; + my $grid_log = Gtk4::Grid->new; + my $i = 0; + for my $log_message (@$logs) { + my $date = $log_message->{date}; + my $message = $log_message->{message}; + my $input_file = $log_message->{input_file}; + my $output_file = $log_message->{output_file}; + my $backup_input_file = $log_message->{backup_input_file}; + my $backup_output_file = $log_message->{backup_output_file}; + $message = "$date $message"; + + if ( defined $input_file ) { + $message .= "\n with input file $input_file"; + } + if ( defined $output_file ) { + $message .= "\n with output file $output_file"; + } + my $label = Gtk4::Label->new($message); + $grid_log->attach($label, 0, $i, 10, 1); + $self->create_button_restore( $grid_log, 11, $i, 'Restore input file', + $backup_input_file, $input_file ); + $self->create_button_restore( $grid_log, 12, $i, 'Restore output file', + $backup_output_file, $output_file ); + $i++; + } + $win->set_child($grid_log); +} + +sub open_save { + my $self = shift; + my $file = shift; + my $dest_file = shift; + my $file_dialog = Gtk4::FileDialog->new; + if ( defined $dest_file ) { + $file_dialog->set_initial_folder( Glib::IO::File::new_for_path($dest_file->parent )); + $file_dialog->set_initial_name( $dest_file->basename ); + } + $file_dialog->save( + $self->_win, + undef, + sub { + my ( $dialog, $res ) = @_; + if ( $res->had_error ) { + return; + } + my $dest_file = $dialog->save_finish($res); + return if !defined $dest_file; + $dest_file = path( $dest_file->get_path ); + logger()->msg('Restoring backup...', $file, $dest_file); + $dest_file->spew_raw( $file->slurp_raw ); + } + ); +} + +sub create_button_restore { + my $self = shift; + my $grid_log = shift; + my $column = shift; + my $row = shift; + my $restore_label = shift; + my $file = shift; + my $dest_file = shift; + if ( !defined $file ) { + return; + } + if ( defined $dest_file ) { + $dest_file = path($dest_file); + } + $file = path($file); + return if !-e $file; + my $button_restore_backup = Gtk4::Button->new_with_label($restore_label); + $button_restore_backup->signal_connect( + clicked => sub { + $self->open_save( $file, $dest_file ); + } + ); + $grid_log->attach($button_restore_backup, $column, $row, 1, 1); +} +1; diff --git a/lib/GEmeTool/View/MainWindow.pm b/lib/GEmeTool/View/MainWindow.pm index e39dedd..3031dba 100644 --- a/lib/GEmeTool/View/MainWindow.pm +++ b/lib/GEmeTool/View/MainWindow.pm @@ -13,6 +13,7 @@ use Data::Dumper; use Path::Tiny; use GEmeTool::Options; use GEmeTool::Save; +use GEmeTool::View::LogWindow; use Moo; @@ -79,11 +80,13 @@ sub activate { my $menu = Glib::IO::Menu->new; my $about = Glib::IO::SimpleAction->new( 'about', undef ); my $open = Glib::IO::SimpleAction->new( 'open', undef ); + my $logs = Glib::IO::SimpleAction->new( 'view_logs', undef ); my $save_as = Glib::IO::SimpleAction->new( 'save_as', undef ); $save_as->set_enabled(0); my $app = $self->_app; $app->add_action($about); $app->add_action($open); + $app->add_action($logs); $app->add_action($save_as); $self->_save_as($save_as); my $save; @@ -99,16 +102,25 @@ sub activate { $self->activate_save; } ); + $logs->signal_connect( + activate => sub { + GEmeTool::View::LogWindow->new(app => $self->_app)->start; + } + ); $about->signal_connect( activate => \&activate_about, ); my $about_menu_item = Glib::IO::MenuItem->new( 'About', 'app.about' ); my $open_menu_item = Glib::IO::MenuItem->new( 'Open', 'app.open' ); + my $logs_menu_item = Glib::IO::MenuItem->new( 'View logs', 'app.view_logs' ); my $submenu_file = Glib::IO::Menu->new; + my $submenu_view = Glib::IO::Menu->new; my $submenu_help = Glib::IO::Menu->new; $submenu_help->append_item($about_menu_item); + $submenu_view->append_item($logs_menu_item); $submenu_file->append_item($open_menu_item); $submenu_file->append_item($save_menu_item); $menu->append_submenu( 'File', $submenu_file ); + $menu->append_submenu( 'View', $submenu_view ); $menu->append_submenu( 'Help', $submenu_help ); $app->set_menubar($menu); my $box = Gtk4::Box->new( 'vertical', 0 ); @@ -158,10 +170,11 @@ sub activate_save { my $win = $self->_win; my $dialog = Gtk4::FileDialog->new; my $options = $self->_options; - my $last_dir = $options->get_last_dir_save; + my $last_dir = $options->get_last_dir_open; if (defined $last_dir && -d $last_dir) { my $curdir = Glib::IO::File::new_for_path($last_dir); $dialog->set_initial_folder($curdir); + $dialog->set_initial_name( path($self->_file)->basename ); } $dialog->save( $win, undef,