fix quoting in AsyncWgetFileGetter again
[videosite.git] / libvideosite.pm
index 8791c64..f9959bf 100644 (file)
@@ -17,6 +17,8 @@ use LWP::UserAgent;
 use Data::Dumper;
 use File::Basename;
 use Cwd qw(realpath);
+use JSON -support_by_pp;
+use File::Temp qw(tempfile);
 use strict;
 
 @ISA = qw(Exporter);
@@ -25,10 +27,15 @@ use strict;
 my @outputstack;
 my $outputprefix;
 my $debug = 0;
+my %debugwindows = ();
 my @grabbers;
 my @getters;
 my $getter;
 my %builtin_config = ();
+my $builtin_config_path;
+my $builtin_config_default;
+my $config_cache = 1;
+my %config_cache = ();
 our $error;
 
 #
@@ -45,7 +52,12 @@ my $defaultconfig = {
             'name' => 'direct',
             '_immutable' => '1',
             'schemas' => {},
-        }
+        },
+        'environment' => {
+            'name' => 'environment',
+            '_immutable' => '1',
+            'schemas' => {},
+        },
     },
     'config-version' => '2',
 };
@@ -66,6 +78,7 @@ my $remote_api = {
     module_path => sub { return dirname(realpath($0)) },
     quote => sub { return $_ },
     reload => sub {},
+    wait_for_child => sub {},
 };
 
 #
@@ -79,7 +92,7 @@ my $videosite_commands = {
     'set' => sub {
         _cmd_set(@_);
     },
-    
+
     'show' => sub {
         _cmd_show(@_);
     },
@@ -113,19 +126,15 @@ my $videosite_commands = {
     },
 
     'debug' => sub {
-        $debug = 1;
-        foreach (@grabbers, @getters) {
-            $_->setdebug(1);
-        }
-        _io('Enabled debugging');
+        _cmd_debug(@_);
     },
 
     'nodebug' => sub {
-        $debug = 0;
-        foreach (@grabbers, @getters) {
-            $_->setdebug(0);
-        }
-        _io('Disabled debugging');
+        _cmd_nodebug(@_);
+    },
+
+    'service' => sub {
+        _cmd_service(@_);
     },
 };
 
@@ -156,7 +165,7 @@ sub _io {
     #
     @text = map { defined($_)?$remote_api->{quote}->($_):'(undef)' } @text;
 
-    $outputstack[0]->(sprintf($format, @text));
+    $outputstack[0]->{io}->(sprintf($format, @text));
 }
 
 #
@@ -206,8 +215,18 @@ sub _init_config_item {
 # Print a message if debug is enabled
 #
 sub _debug {
+    my @data = @_;
+
+    $data[0] = "DEBUG: " . $data[0];
+
+    # Check for global debug
     if ($debug) {
-        _io(@_);
+        _io(@data);
+    } else {
+        # Check if current window is in the per-window-debug list
+        if (exists($debugwindows{$outputstack[0]->{window}})) {
+            _io(@data);
+        }
     }
 }
 
@@ -253,12 +272,13 @@ sub _ploader {
             push(@g, $g);
             $g->register_api({
                 io => \&_io,
+                io_debug => \&_debug,
                 connectors => sub { return _connectorlist('active-connectors') },
                 config_get => \&_config_get,
                 config_set => \&_config_set,
                 config_has => \&_config_has,
+               wait_for_child => $remote_api->{wait_for_child},
             });
-            $g->setdebug($debug);
         } else {
             _io('%s has wrong type (got %s, expected %s)', $p, $g->{'TYPE'}, $type);
             delete($INC{$p});
@@ -266,7 +286,7 @@ sub _ploader {
     }
 
     _debug("Loaded %d plugins", $#g+1);
-    
+
     return @g;
 }
 
@@ -294,36 +314,53 @@ sub _load_modules($) {
 #
 sub _config_get {
     my $path = shift;
+    my $dotpath = join('.', @{$path});
     my $value;
 
-    $value = $remote_api->{config_get}->($path);
-    _debug("config: getting %s=%s", join('.', @{$path}), $value);
+    if ($config_cache && exists($config_cache{$dotpath}) && exists($config_cache{$dotpath}->{value})) {
+        $value = $config_cache{$dotpath}->{value};
+    } else {
+        $value = $remote_api->{config_get}->($path);
+        $config_cache{$dotpath} = {value => $value, has => 1};
+
+    }
 
+    _debug("config: getting %s=%s", $dotpath, $value);
     return $value;
 }
 
 sub _config_set {
     my $path = shift;
+    my $dotpath = join('.', @{$path});
     my $value = shift;
 
-    _debug("config: setting %s=%s", join('.', @{$path}), $value);
+    _debug("config: setting %s=%s", $dotpath, $value);
+    $config_cache{$dotpath} = {value => $value, has => 1};
     return $remote_api->{config_set}->($path, $value);
 }
 
 sub _config_has {
     my $path = shift;
+    my $dotpath = join('.', @{$path});
     my $b;
 
-    $b = $remote_api->{config_has}->($path);
-    _debug("config: testing %s (%s)", join('.', @{$path}), $b?'true':'false');
+    if ($config_cache && exists($config_cache{$dotpath}) && exists($config_cache{$dotpath}->{has})) {
+        $b = $config_cache{$dotpath}->{has};
+    } else {
+        $b = $remote_api->{config_has}->($path);
+        $config_cache{$dotpath}->{has} = $b;
+    }
 
+    _debug("config: testing %s (%s)", $dotpath, $b?'true':'false');
     return $b;
 }
 
 sub _config_del {
     my $path = shift;
+    my $dotpath = join('.', @{$path});
 
-    _debug("config: removing %s", join('.', @{$path}));
+    _debug("config: removing %s", $dotpath);
+    delete($config_cache{$dotpath});
     $remote_api->{config_del}->($path);
 }
 
@@ -355,7 +392,7 @@ sub _config_list_add {
     _config_set($path, join(',', @c));
 }
 
-# 
+#
 # Remove an item from the list
 #
 sub _config_list_del {
@@ -523,6 +560,8 @@ sub _expand_url_shortener {
 # Save the config to durable storage
 #
 sub _cmd_save {
+    my $event = shift;
+
     if ($remote_api->{config_save}->()) {
         _io("Config saved");
     } else {
@@ -534,6 +573,7 @@ sub _cmd_save {
 # Set a configuration element
 #
 sub _cmd_set {
+    my $event = shift;
     my $target = shift;
     my $key = shift;
     my $val = shift;
@@ -553,6 +593,7 @@ sub _cmd_set {
 # Enable a given module
 #
 sub _cmd_enable {
+    my $event = shift;
     my $target = shift;
     my $p;
 
@@ -569,6 +610,7 @@ sub _cmd_enable {
 # Disable given module
 #
 sub _cmd_disable {
+    my $event = shift;
     my $target = shift;
     my $p;
 
@@ -585,6 +627,7 @@ sub _cmd_disable {
 # Show settings for modules
 #
 sub _cmd_show {
+    my $event = shift;
     my $target = shift;
     my $p;
     my $e;
@@ -615,6 +658,7 @@ sub _cmd_show {
 # Show help for the commands
 #
 sub _cmd_help {
+    my $event = shift;
     my $target = shift;
     my $p;
 
@@ -649,6 +693,7 @@ EOT
 # Set the getter to use
 #
 sub _cmd_getter {
+    my $event = shift;
     my $target = shift;
     my $p;
 
@@ -671,6 +716,7 @@ sub _cmd_getter {
 # Show/set the working mode
 #
 sub _cmd_mode {
+    my $event = shift;
     my $mode = shift;
 
     if (defined($mode)) {
@@ -691,6 +737,7 @@ sub _cmd_mode {
 # Manage the connectors
 #
 sub _cmd_connector {
+    my $event = shift;
     my $subcmd = shift;
     my $c;
 
@@ -853,6 +900,85 @@ sub _cmd_connector {
 }
 
 #
+# Enable debug.
+# Global debug if the keyword "all" is given, or just for the
+# current window otherwise
+#
+sub _cmd_debug {
+    my $event = shift;
+    my $scope = shift;
+
+    if (defined($scope) and (lc($scope) eq 'all')) {
+        _io("Global debug enabled");
+        $debug = 1;
+    } else {
+        _io("Debug for this window enabled");
+        $debugwindows{$event->{window}} = 1;
+    }
+}
+
+#
+# Disable debug
+# Disable global debug if the keyword "all" is given (this will
+# also disable all per-window debugs) or just for the current
+# window
+#
+sub _cmd_nodebug {
+    my $event = shift;
+    my $scope = shift;
+
+    if (defined($scope) and (lc($scope) eq 'all')) {
+        $debug = 0;
+        %debugwindows = ();
+        _io("Global debug disabled");
+    } else {
+        delete($debugwindows{$event->{window}});
+        _io("Debug for this window disabled");
+    }
+}
+
+#
+# Handle generic service commands
+#
+sub _cmd_service {
+    my $event = shift;
+    my $subcmd = shift || '';
+
+    $subcmd = lc($subcmd);
+
+    if ($subcmd eq 'cache') {
+        _cmd_service_cache($event, @_);
+    }
+}
+
+
+#
+# Display or clear the content of the config cache
+#
+sub _cmd_service_cache {
+    my $event = shift;
+    my $subcmd = shift;
+
+    $subcmd = 'list' unless defined($subcmd);
+    $subcmd = lc($subcmd);
+
+    if ($subcmd eq 'list') {
+        _io("Content of config cache:");
+        foreach (sort(keys(%config_cache))) {
+            if (exists($config_cache{$_}->{value})) {
+                _io(" %s => %s", $_, $config_cache{$_}->{value});
+            } else {
+                _io(" %s present", $_);
+            }
+        }
+    } elsif ($subcmd eq 'clear') {
+        %config_cache = ();
+        _io("Cache cleared");
+    }
+}
+
+
+#
 # Return the list of loaded grabbers.
 # This is used by the test programs, and is not meant to be
 # used in general.
@@ -869,6 +995,29 @@ sub _grabbers {
 # ==============================================
 #
 sub _builtin_config_init {
+
+    if (defined($builtin_config_path)) {
+        my $filename = File::Spec->catfile($builtin_config_path, 'videosite.json');
+
+        _debug("Trying to load configuration from %s", $filename);
+
+        if (-r $filename) {
+            eval {
+                local $/;
+                open(CONF, '<', $filename);
+                %builtin_config = %{JSON->new->utf8->decode(<CONF>)};
+                close(CONF);
+            } or do {
+                _io("Error loading configuration: %s", $@);
+            }
+        };
+    } elsif (defined($builtin_config_default)) {
+        _debug("Initializing builtin config from external default");
+        foreach (keys(%{$builtin_config_default})) {
+            _debug("Setting %s=%s", $_, $builtin_config_default->{$_});
+            $builtin_config{$_} = $builtin_config_default->{$_};
+        }
+    }
 }
 
 sub _builtin_config_get {
@@ -884,6 +1033,23 @@ sub _builtin_config_has {
 }
 
 sub _builtin_config_save {
+
+    if (defined($builtin_config_path)) {
+        my $filename = File::Spec->catfile($builtin_config_path, 'videosite.json');
+
+        _debug("Attempting to save config to %s", $filename);
+
+        eval {
+            my ($tempfile, $tempfn) = tempfile("videosite.json.XXXXXX", dir => $builtin_config_path);
+            print $tempfile JSON->new->pretty->utf8->encode(\%builtin_config);
+            close($tempfile);
+            rename($tempfn, $filename);
+        } or do {
+            return 0;
+        }
+    }
+
+    return 1;
 }
 
 sub _builtin_config_del {
@@ -982,7 +1148,19 @@ sub register_api {
         $debug = $a->{_debug}->();
     }
 
-    @outputstack = ($remote_api->{'io'});
+    if (exists($a->{_config_path})) {
+        $builtin_config_path = $a->{_config_path}->();
+    }
+
+    if (exists($a->{_config_default})) {
+        $builtin_config_default = $a->{_config_default}->();
+    }
+
+    if (exists($a->{_config_cache})) {
+        $config_cache = $a->{_config_cache}->();
+    }
+
+    @outputstack = ({io => $remote_api->{'io'}, window => ""});
 
     return 1;
 }
@@ -1008,7 +1186,7 @@ sub check_for_link {
         $mode = 'display';
     }
 
-    _push_output($event->{ewpf});
+    _push_output($event);
     $message = _expand_url_shortener($message);
 
     study($message);
@@ -1075,10 +1253,10 @@ sub handle_command {
     my $event = shift;
     my ($cmd, @params) = split(/\s+/, $event->{message});
 
-    _push_output($event->{ewpf});
+    _push_output($event);
 
     if (exists($videosite_commands->{$cmd})) {
-        $videosite_commands->{$cmd}->(@params);
+        $videosite_commands->{$cmd}->($event, @params);
     }
 
     _pop_output();