Merge pull request 'patch-rewrite' (#3) from patch-rewrite into dev

Reviewed-on: #3
This commit is contained in:
Marko Oldenburg 2023-01-05 20:44:25 +01:00
commit 37326cb565
3 changed files with 341 additions and 317 deletions

View File

@ -1,8 +1,8 @@
############################################################################### ###############################################################################
# #
# Developed with Kate # Developed with VSCodium and richterger perl plugin
# #
# (c) 2020-2021 Copyright: Marko Oldenburg (fhemdevelopment@cooltux.net) # (c) 2020-2023 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
# All rights reserved # All rights reserved
# #
# Special thanks goes to: # Special thanks goes to:
@ -29,16 +29,21 @@
# #
############################################################################### ###############################################################################
package main; package FHEM::backupToStorage;
use strict; use strict;
use warnings; use warnings;
use utf8; use utf8;
use FHEM::Meta; use FHEM::Meta;
use FHEM::Services::backupToStorage; require FHEM::Services::backupToStorage;
sub backupToStorage_Initialize { #-- Run before package compilation
BEGIN {
sub ::backupToStorage_Initialize { goto &Initialize }
}
sub Initialize {
my $hash = shift; my $hash = shift;
## Da ich mit package arbeite müssen in die Initialize für die jeweiligen hash Fn Funktionen der Funktionsname ## Da ich mit package arbeite müssen in die Initialize für die jeweiligen hash Fn Funktionen der Funktionsname
@ -76,13 +81,14 @@ sub backupToStorage_Initialize {
=begin html =begin html
<a name="backupToStorage"></a> <a id="backupToStorage"></a>
<h3>backupToStorage</h3> <h3>backupToStorage</h3>
<ul> <ul>
The module offers the possibility to automatically load the created backup files from the backup module onto a storage.<br> The module offers the possibility to automatically load the created backup files from the backup module onto a storage.<br>
<a name="backupToStoragedefine"></a> <a name="backupToStoragedefine"></a>
<br> <br>
<b>Define</b> <a id="backupToStorage-define"></a>
<h4>Define</h4>
<ul> <ul>
<code>define &lt;name&gt; backupToStorage</code> <code>define &lt;name&gt; backupToStorage</code>
<br> <br>
@ -92,28 +98,46 @@ sub backupToStorage_Initialize {
</ul> </ul>
<br> <br>
</ul> </ul>
<a name="backupToStorageattributes"></a> <a id="backupToStorage-attr"></a>
<b>Attributs</b> <h4>Attributs</h4>
<ul> <ul>
<li>bTS_Host - Server name where the storage is located</li> <a id="backupToStorage-attr-bTS_Host"></a>
<li>bTS_User - remote user for login</li> <li><i>bTS_Host</i>
<li>bTS_Path - remote path where the upload file should go. e.g. Nextcloud &lt;/FHEM-Backup&gt;</li> Server name where the storage is located
<li>bTS_Type - Storage Type, default is Nextcloud</li> </li>
<a id="backupToStorage-attr-bTS_User"></a>
<li><i>bTS_User</i>
remote user for login
</li>
<a id="backupToStorage-attr-bTS_Path"></a>
<li><i>bTS_Path</i>
remote path where the upload file should go. e.g. Nextcloud &lt;/FHEM-Backup&gt;
</li>
<a id="backupToStorage-attr-bTS_Type"></a>
<li><i>bTS_Type</i>
Storage Type, default is Nextcloud
</li>
</ul> </ul>
<br> <br>
<a name="backupToStorageset"></a> <a id="backupToStorage-set"></a>
<b>Set</b> <h4>Set</h4>
<ul> <ul>
<li>addpassword - puts the storage password in the keyfile / !!! don't use = !!!</li> <a id="backupToStorage-set-addpassword"></a>
<li>deletepassword - removes the storage password from the keyfile</li> <li><i>addpassword</i>
puts the storage password in the keyfile / !!! don't use = !!!
</li>
<a id="backupToStorage-set-deletepassword"></a>
<li><i>deletepassword</i>
removes the storage password from the keyfile
</li>
</ul> </ul>
<br> <br>
<a name="backupToStoragereadings"></a> <a id="backupToStorage-readings"></a>
<b>Readings</b> <b>Readings</b>
<ul> <ul>
<li>state - shows the current status of the module</li> <li><b>state</b> - shows the current status of the module</li>
<li>fhemBackupFile - the path of the last backup file is automatically set by the backup module</li> <li><b>fhemBackupFile</b> - the path of the last backup file is automatically set by the backup module</li>
<li>uploadState - Status of the last upload.</li> <li><b>uploadState</b> - Status of the last upload.</li>
</ul> </ul>
</ul> </ul>
@ -121,13 +145,14 @@ sub backupToStorage_Initialize {
=begin html_DE =begin html_DE
<a name="backupToStorage"></a> <a id="backupToStorage"></a>
<h3>backupToStorage</h3> <h3>backupToStorage</h3>
<ul> <ul>
Das Modul bietet die M&ouml;glichkeit die erstellten Backupdateien vom Modul backup automatisiert auf ein Storage zu laden.<br> Das Modul bietet die M&ouml;glichkeit die erstellten Backupdateien vom Modul backup automatisiert auf ein Storage zu laden.<br>
<a name="backupToStoragedefine"></a> <a name="backupToStoragedefine"></a>
<br> <br>
<b>Define</b> <a id="backupToStorage-define"></a>
<h4>Define</h4>
<ul> <ul>
<code>define &lt;name&gt; backupToStorage</code> <code>define &lt;name&gt; backupToStorage</code>
<br> <br>
@ -137,29 +162,46 @@ sub backupToStorage_Initialize {
</ul> </ul>
<br> <br>
</ul> </ul>
<a name="backupToStorageattributes"></a> <a id="backupToStorage-attr"></a>
<b>Attribute</b> <h4>Attribute</h4>
<ul> <ul>
<li>bTS_Host - Servername wo sich das Storage drauf befindet</li> <a id="backupToStorage-attr-bTS_Host"></a>
<li>bTS_User - remote User f&uuml;r den Login</li> <li><i>bTS_Host</i>
<li>bTS_Path - remote Path wohin das uploadfile soll. z.B. Nextcloud &lt;/FHEM-Backup&gt;</li> Servername wo sich das Storage drauf befindet
<li>bTS_Type - Storage Type, default ist Nextcloud</li> </li>
<li>bTS_Type - Storage Type, default ist Nextcloud</li> <a id="backupToStorage-attr-bTS_User"></a>
<li><i>bTS_User</i>
remote User f&uuml;r den Login
</li>
<a id="backupToStorage-attr-bTS_Path"></a>
<li><i>bTS_Path</i>
remote Path wohin das uploadfile soll. z.B. Nextcloud &lt;/FHEM-Backup&gt;
</li>
<a id="backupToStorage-attr-bTS_Type"></a>
<li><i>bTS_Type</i>
Storage Type, default ist Nextcloud
</li>
</ul> </ul>
<br> <br>
<a name="backupToStorageset"></a> <a id="backupToStorage-set"></a>
<b>Set</b> <h4>Set</h4>
<ul> <ul>
<li>addpassword - setzt das Storage Passwort ins Keyfile / !!!Keine = verwenden!!!</li> <a id="backupToStorage-set-addpassword"></a>
<li>deletepassword - entfernt das Storage Passwort aus dem Keyfile</li> <li><i>addpassword</i><br>
setzt das Storage Passwort ins Keyfile / !!!Keine = verwenden!!!
</li>
<a id="backupToStorage-set-deletepassword"></a>
<li><i>deletepassword</i><br>
entfernt das Storage Passwort aus dem Keyfile
</li>
</ul> </ul>
<br> <br>
<a name="backupToStoragereadings"></a> <a id="backupToStorage-readings"></a>
<b>Readings</b> <h4>Readings</h4>
<ul> <ul>
<li>state - zeigt den aktuellen Status des Modules an</li> <li><b>state</b> - zeigt den aktuellen Status des Modules an</li>
<li>fhemBackupFile - der Pfad des letzten Backupfiles, wird automatisch vom backup Modul gesetzt</li> <li><b>fhemBackupFile</b> - der Pfad des letzten Backupfiles, wird automatisch vom backup Modul gesetzt</li>
<li>uploadState - Status des letzten uploads.</li> <li><b>uploadState</b> - Status des letzten uploads.</li>
</ul> </ul>
</ul> </ul>
@ -181,15 +223,15 @@ sub backupToStorage_Initialize {
], ],
"release_status": "devepolment", "release_status": "devepolment",
"license": "GPL_2", "license": "GPL_2",
"version": "v1.3.1", "version": "v2.0.0",
"author": [ "author": [
"Marko Oldenburg <fhemsupport@cooltux.net>" "Marko Oldenburg <fhemdevelopment@cooltux.net>"
], ],
"x_fhem_maintainer": [ "x_fhem_maintainer": [
"CoolTux" "CoolTux"
], ],
"x_fhem_maintainer_github": [ "x_fhem_maintainer_github": [
"LeonGaultier" "CoolTuxNet"
], ],
"prereqs": { "prereqs": {
"runtime": { "runtime": {

View File

@ -1,2 +1,2 @@
UPD 2021-11-09_13:08:21 6508 FHEM/98_backupToStorage.pm UPD 2023-01-05_20:43:08 7708 FHEM/98_backupToStorage.pm
UPD 2021-11-09_13:55:09 24286 lib/FHEM/Services/backupToStorage.pm UPD 2023-01-05_20:06:46 23352 lib/FHEM/Services/backupToStorage.pm

View File

@ -1,8 +1,8 @@
############################################################################### ###############################################################################
# #
# Developed with Kate # Developed with VSCodium and richterger perl plugin
# #
# (c) 2020-2021 Copyright: Marko Oldenburg (fhemdevelopment@cooltux.net) # (c) 2020-2022 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
# All rights reserved # All rights reserved
# #
# Special thanks goes to: # Special thanks goes to:
@ -37,7 +37,15 @@ use utf8;
use GPUtils qw(GP_Import); use GPUtils qw(GP_Import);
use Data::Dumper; #only for Debugging BEGIN {
# Import from main context
GP_Import(
qw( init_done
defs
)
);
}
# try to use JSON::MaybeXS wrapper # try to use JSON::MaybeXS wrapper
# for chance of better performance + open code # for chance of better performance + open code
@ -45,15 +53,11 @@ eval {
require JSON::MaybeXS; require JSON::MaybeXS;
import JSON::MaybeXS qw( decode_json encode_json ); import JSON::MaybeXS qw( decode_json encode_json );
1; 1;
}; } or do {
if ($@) {
$@ = undef;
# try to use JSON wrapper # try to use JSON wrapper
# for chance of better performance # for chance of better performance
eval { eval {
# JSON preference order # JSON preference order
local $ENV{PERL_JSON_BACKEND} = local $ENV{PERL_JSON_BACKEND} =
'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP'
@ -62,10 +66,7 @@ if ($@) {
require JSON; require JSON;
import JSON qw( decode_json encode_json ); import JSON qw( decode_json encode_json );
1; 1;
}; } or do {
if ($@) {
$@ = undef;
# In rare cases, Cpanel::JSON::XS may # In rare cases, Cpanel::JSON::XS may
# be installed but JSON|JSON::MaybeXS not ... # be installed but JSON|JSON::MaybeXS not ...
@ -73,10 +74,7 @@ if ($@) {
require Cpanel::JSON::XS; require Cpanel::JSON::XS;
import Cpanel::JSON::XS qw(decode_json encode_json); import Cpanel::JSON::XS qw(decode_json encode_json);
1; 1;
}; } or do {
if ($@) {
$@ = undef;
# In rare cases, JSON::XS may # In rare cases, JSON::XS may
# be installed but JSON not ... # be installed but JSON not ...
@ -84,10 +82,7 @@ if ($@) {
require JSON::XS; require JSON::XS;
import JSON::XS qw(decode_json encode_json); import JSON::XS qw(decode_json encode_json);
1; 1;
}; } or do {
if ($@) {
$@ = undef;
# Fallback to built-in JSON which SHOULD # Fallback to built-in JSON which SHOULD
# be available since 5.014 ... # be available since 5.014 ...
@ -95,63 +90,31 @@ if ($@) {
require JSON::PP; require JSON::PP;
import JSON::PP qw(decode_json encode_json); import JSON::PP qw(decode_json encode_json);
1; 1;
}; } or do {
if ($@) {
$@ = undef;
# Fallback to JSON::backportPP in really rare cases # Fallback to JSON::backportPP in really rare cases
require JSON::backportPP; require JSON::backportPP;
import JSON::backportPP qw(decode_json encode_json); import JSON::backportPP qw(decode_json encode_json);
1; 1;
} };
} };
} };
} };
} };
## Import der FHEM Funktionen
#-- Run before package compilation
BEGIN {
# Import from main context
GP_Import(
qw(
readingsSingleUpdate
readingsBulkUpdate
readingsBeginUpdate
readingsEndUpdate
ReadingsVal
ReadingsAge
gettimeofday
InternalTimer
defs
modules
IsDisabled
setKeyValue
getKeyValue
getUniqueId
Log3
CommandAttr
attr
AttrVal
deviceEvents
init_done
devspec2array
DoTrigger
HttpUtils_NonblockingGet)
);
}
sub Define { sub Define {
use version 0.60;
my $hash = shift // return; my $hash = shift // return;
my $aArg = shift // return; my $aArg = shift // return;
return $@ unless ( FHEM::Meta::SetInternals($hash) ); return $@ unless ( FHEM::Meta::SetInternals($hash) );
use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
$version = FHEM::Meta::Get( $hash, 'version' );
our $VERSION = $version;
return q{only one backupToStorage instance allowed} return q{only one backupToStorage instance allowed}
if ( devspec2array('TYPE=backupToStorage') > 1 ) if ( ::devspec2array('TYPE=backupToStorage') > 1 )
; # es wird geprüft ob bereits eine Instanz unseres Modules existiert,wenn ja wird abgebrochen ; # es wird geprüft ob bereits eine Instanz unseres Modules existiert,wenn ja wird abgebrochen
return q{too few parameters: define <name> backupToStorage} return q{too few parameters: define <name> backupToStorage}
if ( scalar( @{$aArg} ) != 2 ); if ( scalar( @{$aArg} ) != 2 );
@ -159,9 +122,9 @@ sub Define {
my $name = shift @$aArg; my $name = shift @$aArg;
$hash->{VERSION} = version->parse($VERSION)->normal; $hash->{VERSION} = version->parse($VERSION)->normal;
$hash->{NOTIFYDEV} = 'global,' . $name; $hash->{NOTIFYDEV} = 'global,' . $name;
$hash->{STORAGETYPE} = AttrVal( $name, 'bTS_Type', 'Nextcloud' ); $hash->{STORAGETYPE} = ::AttrVal( $name, 'bTS_Type', 'Nextcloud' );
Log3( $name, 3, qq{backupToStorage ($name) - defined} ); ::Log3( $name, 3, qq{backupToStorage ($name) - defined} );
return; return;
} }
@ -170,7 +133,7 @@ sub Undef {
my $hash = shift; my $hash = shift;
my $name = shift; my $name = shift;
Log3( $name, 3, q{qbackupToStorage ($name) - delete device $name} ); ::Log3( $name, 3, q{qbackupToStorage ($name) - delete device $name} );
return; return;
} }
@ -202,14 +165,15 @@ sub Notify {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $devname = $dev->{NAME}; my $devname = $dev->{NAME};
my $devtype = $dev->{TYPE}; my $devtype = $dev->{TYPE};
my $events = deviceEvents( $dev, 1 ); my $events = ::deviceEvents( $dev, 1 );
_CheckIsDisabledAfterSetAttr($hash) _CheckIsDisabledAfterSetAttr($hash)
if ( ( if (
( (
grep m{^DELETEATTR.$name.(disable|disabledForIntervals)$}xms, (
grep { /^DELETEATTR.$name.(disable|disabledForIntervals)$/x }
@{$events} @{$events}
or grep m{^ATTR.$name.(disable|disabledForIntervals).\S+$}xms, or grep { /^ATTR.$name.(disable|disabledForIntervals).\S+$/x }
@{$events} @{$events}
) )
&& $devname eq 'global' && $devname eq 'global'
@ -219,18 +183,13 @@ sub Notify {
); );
return if ( !$events return if ( !$events
|| IsDisabled($name) ); || ::IsDisabled($name) );
::Log3( $name, 4,
Log3( $name, 4, qq{backupToStorage ($name) - Devname: $devname Name: $name Notify: } );
qq{backupToStorage ($name) -
Devname: $devname
Name: $name
Notify: } . Dumper $events
); # mit Dumper
PushToStorage($hash) PushToStorage($hash)
if ( ( grep m{^backup.done(.+)?$}xms, @{$events} ) if ( ( grep { /^backup.done(.+)?$/x } @{$events} )
&& $devname eq 'global' && $devname eq 'global'
&& $init_done ); && $init_done );
@ -239,16 +198,16 @@ sub Notify {
( (
( (
( (
grep m{^DELETEATTR.$name.(bTS_Host|bTS_User)$}xms, grep { /^DELETEATTR.$name.(bTS_Host|bTS_User)$/x }
@{$events} @{$events}
or grep m{^ATTR.$name.(bTS_Host|bTS_User).\S+$}xms, or grep { /^ATTR.$name.(bTS_Host|bTS_User).\S+$/x }
@{$events} @{$events}
) )
&& $devname eq 'global' && $devname eq 'global'
) )
|| ( || (
( (
$devname eq $name && grep m{^password.(add|remove)$}xms, $devname eq $name && grep { /^password.(add|remove)$/x }
@{$events} @{$events}
) )
) )
@ -260,8 +219,8 @@ sub Notify {
$hash, 'state', $hash, 'state',
( (
( (
AttrVal( $name, 'bTS_Host', 'none' ) eq 'none' ::AttrVal( $name, 'bTS_Host', 'none' ) eq 'none'
|| AttrVal( $name, 'bTS_User', 'none' ) eq 'none' || ::AttrVal( $name, 'bTS_User', 'none' ) eq 'none'
|| !defined( ReadPassword( $hash, $name ) ) || !defined( ReadPassword( $hash, $name ) )
) )
? 'please set storage account credentials first' ? 'please set storage account credentials first'
@ -271,16 +230,13 @@ sub Notify {
) )
if ( if (
( (
( grep m{^DEFINED.$name$}xms, @{$events} ) ( grep { /^DEFINED.$name$/x } @{$events} )
&& $devname eq 'global' && $devname eq 'global'
&& $init_done && $init_done
) )
|| ( || ( grep { /^INITIALIZED$/x } @{$events}
grep m{^INITIALIZED$}xms, or grep { /^REREADCFG$/x } @{$events}
@{$events} or grep m{^REREADCFG$}xms, or grep { /^MODIFIED.$name$/x } @{$events} )
@{$events} or grep m{^MODIFIED.$name$}xms,
@{$events}
)
&& $devname eq 'global' && $devname eq 'global'
); );
@ -297,7 +253,7 @@ sub Set {
if ( lc $cmd eq 'addpassword' ) { if ( lc $cmd eq 'addpassword' ) {
return q{please set Attribut bTS_User first} return q{please set Attribut bTS_User first}
if ( AttrVal( $name, 'bTS_User', 'none' ) eq 'none' ); if ( ::AttrVal( $name, 'bTS_User', 'none' ) eq 'none' );
return qq{usage: "$cmd" <password>} return qq{usage: "$cmd" <password>}
if ( scalar( @{$aArg} ) != 1 ); if ( scalar( @{$aArg} ) != 1 );
@ -343,28 +299,39 @@ sub Attr {
my $attrName = shift; my $attrName = shift;
my $attrVal = shift; my $attrVal = shift;
if ( $attrName eq 'disable' if ( $attrName eq 'disable'
|| $attrName eq 'disabledForIntervals' ) { || $attrName eq 'disabledForIntervals' )
{
if ( $cmd eq 'set' ) { if ( $cmd eq 'set' ) {
if ( $attrName eq 'disabledForIntervals' ) { if ( $attrName eq 'disabledForIntervals' ) {
return return
'check disabledForIntervals Syntax HH:MM-HH:MM or HH:MM-HH:MM HH:MM-HH:MM ...' 'check disabledForIntervals Syntax HH:MM-HH:MM or HH:MM-HH:MM HH:MM-HH:MM ...'
if ( $attrVal !~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ ); if ( $attrVal !~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/x );
Log3( $name, 3, qq{backupToStorage ($name) - disabledForIntervals} ); ::Log3( $name, 3,
qq{backupToStorage ($name) - disabledForIntervals} );
} }
elsif ( $attrName eq 'disable' ) { elsif ( $attrName eq 'disable' ) {
Log3( $name, 3, qq{backupToStorage ($name) - disabled} ); ::Log3( $name, 3, qq{backupToStorage ($name) - disabled} );
} }
} }
InternalTimer( gettimeofday() + 1, ::InternalTimer(
'FHEM::Services::backupToStorage::_CheckIsDisabledAfterSetAttr', $hash, 0 ); ::gettimeofday() + 1,
'FHEM::Services::backupToStorage::_CheckIsDisabledAfterSetAttr',
$hash, 0
);
} }
elsif ( $attrName eq 'bTS_Type' ) { elsif ( $attrName eq 'bTS_Type' ) {
InternalTimer( gettimeofday() + 1, ::InternalTimer(
sub { $hash->{STORAGETYPE} = AttrVal($name,'bTS_Type','Nextcloud'); }, $hash, 0 ); ::gettimeofday() + 1,
sub {
$hash->{STORAGETYPE} =
::AttrVal( $name, 'bTS_Type', 'Nextcloud' );
},
$hash,
0
);
} }
return; return;
@ -374,14 +341,19 @@ sub _CheckIsDisabledAfterSetAttr {
my $hash = shift; my $hash = shift;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $state = ( IsDisabled($name) my $state = (
::IsDisabled($name)
? 'inactive' ? 'inactive'
: 'ready' ); : 'ready'
);
Log3( $name, 3, qq{backupToStorage ($name) - _CheckIsDisabledAfterSetAttr} ); ::Log3( $name, 3,
qq{backupToStorage ($name) - _CheckIsDisabledAfterSetAttr} );
readingsSingleUpdate( $hash, 'state', $state, 1 ) readingsSingleUpdate( $hash, 'state', $state, 1 )
if ( ReadingsVal($name, 'state', 'ready' ) ne $state ); if ( ::ReadingsVal( $name, 'state', 'ready' ) ne $state );
return;
} }
sub Rename { sub Rename {
@ -391,7 +363,7 @@ sub Rename {
my $hash = $defs{$new}; my $hash = $defs{$new};
StorePassword( $hash, $new, ReadPassword( $hash, $old ) ); StorePassword( $hash, $new, ReadPassword( $hash, $old ) );
setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef ); ::setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef );
return; return;
} }
@ -401,66 +373,67 @@ sub PushToStorage {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3( $name, 4, qq{backupToStorage ($name) - push to storage function} ); ::Log3( $name, 4, qq{backupToStorage ($name) - push to storage function} );
return Log3( $name, 4, qq{backupToStorage ($name) - fhemBackupFile Reading to old} ) return ::Log3( $name, 4,
if ( ReadingsAge($name,'fhemBackupFile',1) > 3600 ); qq{backupToStorage ($name) - fhemBackupFile Reading to old} )
if ( ::ReadingsAge( $name, 'fhemBackupFile', 1 ) > 3600 );
Log3( $name, 4, qq{backupToStorage ($name) - after readings age return} );
::Log3( $name, 4, qq{backupToStorage ($name) - after readings age return} );
if ( $hash->{STORAGETYPE} eq 'SynologyFileStation' ) { if ( $hash->{STORAGETYPE} eq 'SynologyFileStation' ) {
} }
else { else {
require "SubProcess.pm"; require SubProcess;
my $subprocess = SubProcess->new( { onRun => \&FileUpload } ); my $subprocess = SubProcess->new( { onRun => \&FileUpload } );
my $backupFile = ReadingsVal( $name, 'fhemBackupFile', 'none' ); my $backupFile = ::ReadingsVal( $name, 'fhemBackupFile', 'none' );
my @fileNameAtStorage_array = split( '/', $backupFile ); my @fileNameAtStorage_array = split( '/', $backupFile );
my $fileNameAtStorage = $fileNameAtStorage_array[$#fileNameAtStorage_array]; my $fileNameAtStorage =
$fileNameAtStorage_array[$#fileNameAtStorage_array];
$subprocess->{curl} = qx(which curl); $subprocess->{curl} = qx(which curl);
chomp( $subprocess->{curl} ); chomp( $subprocess->{curl} );
$subprocess->{fhemhost} = qx(hostname -f); $subprocess->{fhemhost} = qx(hostname -f);
chomp( $subprocess->{fhemhost} ); chomp( $subprocess->{fhemhost} );
$subprocess->{type} = $hash->{STORAGETYPE}; $subprocess->{type} = $hash->{STORAGETYPE};
$subprocess->{host} = AttrVal( $name, 'bTS_Host', '' ); $subprocess->{host} = ::AttrVal( $name, 'bTS_Host', '' );
$subprocess->{user} = AttrVal( $name, 'bTS_User', '' ); $subprocess->{user} = ::AttrVal( $name, 'bTS_User', '' );
$subprocess->{pass} = ReadPassword( $hash, $name ); $subprocess->{pass} = ReadPassword( $hash, $name );
$subprocess->{path} = AttrVal( $name, 'bTS_Path', '' ); $subprocess->{path} = ::AttrVal( $name, 'bTS_Path', '' );
$subprocess->{backupfile} = $backupFile; $subprocess->{backupfile} = $backupFile;
$subprocess->{fileNameAtStorage} = $fileNameAtStorage; $subprocess->{fileNameAtStorage} = $fileNameAtStorage;
$subprocess->{proto} = AttrVal( $name, 'bTS_Proto', 'https' ); $subprocess->{proto} = ::AttrVal( $name, 'bTS_Proto', 'https' );
$subprocess->{loglevel} = AttrVal( $name, 'verbose', 3 ); $subprocess->{loglevel} = ::AttrVal( $name, 'verbose', 3 );
my $pid = $subprocess->run(); my $pid = $subprocess->run();
readingsSingleUpdate( $hash, 'state', ' file upload in progress', 1 ); readingsSingleUpdate( $hash, 'state', ' file upload in progress', 1 );
if ( !defined($pid) ) { if ( !defined($pid) ) {
Log3( $name, 1, ::Log3( $name, 1,
qq{backupToStorage ($name) - Cannot execute command asynchronously} ); qq{backupToStorage ($name) - Cannot execute command asynchronously}
);
CleanSubprocess($hash); CleanSubprocess($hash);
readingsSingleUpdate( $hash, 'state', readingsSingleUpdate( $hash, 'state',
'Cannot execute command asynchronously', 1 ); 'Cannot execute command asynchronously', 1 );
return undef; return;
} }
Log3( $name, 4, ::Log3( $name, 4,
qq{backupToStorage ($name) - execute command asynchronously (PID="$pid")} qq{backupToStorage ($name) - execute command asynchronously (PID="$pid")}
); );
$hash->{".fhem"}{subprocess} = $subprocess; $hash->{".fhem"}{subprocess} = $subprocess;
InternalTimer( gettimeofday() + 1, ::InternalTimer( ::gettimeofday() + 1,
"FHEM::Services::backupToStorage::PollChild", $hash ); "FHEM::Services::backupToStorage::PollChild", $hash );
} }
Log3( $hash, 4, ::Log3( $hash, 4,
qq{backupToStorage ($name) - control passed back to main loop.} ); qq{backupToStorage ($name) - control passed back to main loop.} );
return; return;
@ -471,59 +444,61 @@ sub KeepLastN {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3( $name, 4, qq{backupToStorage ($name) - Keep Last N at Storage function} ); ::Log3( $name, 4,
qq{backupToStorage ($name) - Keep Last N at Storage function} );
if ( $hash->{STORAGETYPE} eq 'SynologyFileStation' ) { if ( $hash->{STORAGETYPE} eq 'SynologyFileStation' ) {
} }
else { else {
require "SubProcess.pm"; require SubProcess;
my $subprocess = SubProcess->new( { onRun => \&CleanUp } ); my $subprocess = SubProcess->new( { onRun => \&CleanUp } );
my $backupFile = ReadingsVal( $name, 'fhemBackupFile', 'none' ); my $backupFile = ::ReadingsVal( $name, 'fhemBackupFile', 'none' );
my @fileNameAtStorage_array = split( '/', $backupFile ); my @fileNameAtStorage_array = split( '/', $backupFile );
my $fileNameAtStorage = $fileNameAtStorage_array[$#fileNameAtStorage_array]; my $fileNameAtStorage =
$fileNameAtStorage_array[$#fileNameAtStorage_array];
$subprocess->{curl} = qx(which curl); $subprocess->{curl} = qx(which curl);
chomp( $subprocess->{curl} ); chomp( $subprocess->{curl} );
$subprocess->{type} = $hash->{STORAGETYPE}; $subprocess->{type} = $hash->{STORAGETYPE};
$subprocess->{host} = AttrVal( $name, 'bTS_Host', '' ); $subprocess->{host} = ::AttrVal( $name, 'bTS_Host', '' );
$subprocess->{user} = AttrVal( $name, 'bTS_User', '' ); $subprocess->{user} = ::AttrVal( $name, 'bTS_User', '' );
$subprocess->{pass} = ReadPassword( $hash, $name ); $subprocess->{pass} = ReadPassword( $hash, $name );
$subprocess->{path} = AttrVal( $name, 'bTS_Path', '' ); $subprocess->{path} = ::AttrVal( $name, 'bTS_Path', '' );
$subprocess->{fileNameAtStorage} = $fileNameAtStorage; $subprocess->{fileNameAtStorage} = $fileNameAtStorage;
$subprocess->{proto} = AttrVal( $name, 'bTS_Proto', 'https' ); $subprocess->{proto} = ::AttrVal( $name, 'bTS_Proto', 'https' );
$subprocess->{loglevel} = AttrVal( $name, 'verbose', 3 ); $subprocess->{loglevel} = ::AttrVal( $name, 'verbose', 3 );
$subprocess->{keeplastn} = AttrVal( $name, 'bTS_KeepLastBackups', 5 ); $subprocess->{keeplastn} = ::AttrVal( $name, 'bTS_KeepLastBackups', 5 );
my $pid = $subprocess->run(); my $pid = $subprocess->run();
readingsSingleUpdate( $hash, 'state', ' clean up pass last N in progress', 1 ); readingsSingleUpdate( $hash, 'state',
' clean up pass last N in progress', 1 );
if ( !defined($pid) ) { if ( !defined($pid) ) {
Log3( $name, 1, ::Log3( $name, 1,
qq{backupToStorage ($name) - Cannot execute command asynchronously} ); qq{backupToStorage ($name) - Cannot execute command asynchronously}
);
CleanSubprocess($hash); CleanSubprocess($hash);
readingsSingleUpdate( $hash, 'state', readingsSingleUpdate( $hash, 'state',
'Cannot execute command asynchronously', 1 ); 'Cannot execute command asynchronously', 1 );
return undef; return;
} }
Log3( $name, 4, ::Log3( $name, 4,
qq{backupToStorage ($name) - execute command asynchronously (PID="$pid")} qq{backupToStorage ($name) - execute command asynchronously (PID="$pid")}
); );
$hash->{".fhem"}{subprocess} = $subprocess; $hash->{".fhem"}{subprocess} = $subprocess;
InternalTimer( gettimeofday() + 1, ::InternalTimer( ::gettimeofday() + 1,
"FHEM::Services::backupToStorage::PollChild", $hash ); "FHEM::Services::backupToStorage::PollChild", $hash );
} }
Log3( $hash, 4, ::Log3( $hash, 4,
qq{backupToStorage ($name) - control passed back to main loop.} ); qq{backupToStorage ($name) - control passed back to main loop.} );
return; return;
@ -539,26 +514,29 @@ sub PollChild {
my $json = $subprocess->readFromChild(); my $json = $subprocess->readFromChild();
if ( !defined($json) ) { if ( !defined($json) ) {
Log3( $name, 5, ::Log3( $name, 5,
qq{backupToStorage ($name) - still waiting ($subprocess->{lasterror}).} qq{backupToStorage ($name) - still waiting ($subprocess->{lasterror}).}
); );
InternalTimer( gettimeofday() + 1, ::InternalTimer( ::gettimeofday() + 1,
"FHEM::Services::backupToStorage::PollChild", $hash ); "FHEM::Services::backupToStorage::PollChild", $hash );
return; return;
} }
else { else {
Log3( $name, 4, ::Log3( $name, 4,
qq{backupToStorage ($name) - got result from asynchronous parsing: $json} ); qq{backupToStorage ($name) - got result from asynchronous parsing: $json}
);
$subprocess->wait(); $subprocess->wait();
Log3( $name, 4, ::Log3( $name, 4,
qq{backupToStorage ($name) - asynchronous finished.} ); qq{backupToStorage ($name) - asynchronous finished.} );
CleanSubprocess($hash); CleanSubprocess($hash);
WriteReadings( $hash, $json ); WriteReadings( $hash, $json );
} }
} }
return;
} }
###################################### ######################################
@ -577,15 +555,15 @@ sub FileUpload {
. $returnString . "\n" . $returnString . "\n"
if ( $subprocess->{loglevel} > 4 ); if ( $subprocess->{loglevel} > 4 );
if ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm
if ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/m and $returnString =~ /\s\s<o:hint xmlns:o="o:">(.*)<\/o:hint>/xm )
and $returnString =~ /\s\s<o:hint xmlns:o="o:">(.*)<\/o:hint>/m ) { {
$response->{ncUpload} = $1; $response->{ncUpload} = $1;
} }
elsif ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/m ) { elsif ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm ) {
$response->{ncUpload} = 'upload successfully'; $response->{ncUpload} = 'upload successfully';
} }
elsif ( $returnString =~ /(curl:\s.*)/ ){ elsif ( $returnString =~ /(curl:\s.*)/x ) {
$response->{ncUpload} = $1; $response->{ncUpload} = $1;
} }
else { else {
@ -618,7 +596,8 @@ sub ExecuteNCupload {
$command .= $subprocess->{user}; $command .= $subprocess->{user};
$command .= $subprocess->{path}; $command .= $subprocess->{path};
$command .= '/'; $command .= '/';
$command .= $subprocess->{fhemhost} . '-' . $subprocess->{fileNameAtStorage}; $command .=
$subprocess->{fhemhost} . '-' . $subprocess->{fileNameAtStorage};
$command .= '"'; $command .= '"';
return ExecuteCommand($command); return ExecuteCommand($command);
@ -637,15 +616,15 @@ sub CleanUp {
. $returnString . "\n" . $returnString . "\n"
if ( $subprocess->{loglevel} > 4 ); if ( $subprocess->{loglevel} > 4 );
if ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm
if ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/m and $returnString =~ /\s\s<o:hint xmlns:o="o:">(.*)<\/o:hint>/xm )
and $returnString =~ /\s\s<o:hint xmlns:o="o:">(.*)<\/o:hint>/m ) { {
$response->{ncUpload} = $1; $response->{ncUpload} = $1;
} }
elsif ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/m ) { elsif ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm ) {
$response->{ncUpload} = 'upload successfully'; $response->{ncUpload} = 'upload successfully';
} }
elsif ( $returnString =~ /(curl:\s.*)/ ){ elsif ( $returnString =~ /(curl:\s.*)/x ) {
$response->{ncUpload} = $1; $response->{ncUpload} = $1;
} }
else { else {
@ -676,7 +655,8 @@ sub ExecuteNCfetchFileList {
$command .= '/remote.php/dav/files/'; $command .= '/remote.php/dav/files/';
$command .= $subprocess->{user}; $command .= $subprocess->{user};
$command .= $subprocess->{path}; $command .= $subprocess->{path};
$command .= '" --data \'<?xml version="1.0" encoding="UTF-8"?><d:propfind xmlns:d="DAV:"><d:prop xmlns:oc="http://owncloud.org/ns"><d:getlastmodified/></d:prop></d:propfind>\''; $command .=
'" --data \'<?xml version="1.0" encoding="UTF-8"?><d:propfind xmlns:d="DAV:"><d:prop xmlns:oc="http://owncloud.org/ns"><d:getlastmodified/></d:prop></d:propfind>\'';
return ExecuteCommand($command); return ExecuteCommand($command);
} }
@ -700,7 +680,8 @@ sub ExecuteNCremoveFile {
} }
sub ExecuteCommand { sub ExecuteCommand {
my $command = join q{ }, @_; my @options = @_;
my $command = join q{ }, @options;
return ( $_ = qx{$command 2>&1}, $? >> 8 ); return ( $_ = qx{$command 2>&1}, $? >> 8 );
} }
@ -714,7 +695,9 @@ sub CleanSubprocess {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
delete( $hash->{".fhem"}{subprocess} ); delete( $hash->{".fhem"}{subprocess} );
Log3( $name, 4, qq{backupToStorage ($name) - clean Subprocess} ); ::Log3( $name, 4, qq{backupToStorage ($name) - clean Subprocess} );
return;
} }
sub StorePassword { sub StorePassword {
@ -723,10 +706,10 @@ sub StorePassword {
my $password = shift; my $password = shift;
my $index = $hash->{TYPE} . "_" . $name . "_passwd"; my $index = $hash->{TYPE} . "_" . $name . "_passwd";
my $key = getUniqueId() . $index; my $key = ::getUniqueId() . $index;
my $enc_pwd = ""; my $enc_pwd = "";
if ( eval "use Digest::MD5;1" ) { if ( eval { use Digest::MD5; 1 } ) {
$key = Digest::MD5::md5_hex( unpack "H*", $key ); $key = Digest::MD5::md5_hex( unpack "H*", $key );
$key .= Digest::MD5::md5_hex($key); $key .= Digest::MD5::md5_hex($key);
@ -739,8 +722,8 @@ sub StorePassword {
$key = $encode . $key; $key = $encode . $key;
} }
my $err = setKeyValue( $index, $enc_pwd ); my $err = ::setKeyValue( $index, $enc_pwd );
DoTrigger( $name, 'password add' ); ::DoTrigger( $name, 'password add' );
return qq{error while saving the password - $err} return qq{error while saving the password - $err}
if ( defined($err) ); if ( defined($err) );
@ -753,30 +736,31 @@ sub ReadPassword {
my $name = shift; my $name = shift;
my $index = $hash->{TYPE} . "_" . $name . "_passwd"; my $index = $hash->{TYPE} . "_" . $name . "_passwd";
my $key = getUniqueId() . $index; my $key = ::getUniqueId() . $index;
my ( $password, $err ); my ( $password, $err );
Log3( $name, 4, qq{backupToStorage ($name) - Read password from file} ); ::Log3( $name, 4, qq{backupToStorage ($name) - Read password from file} );
( $err, $password ) = getKeyValue($index); ( $err, $password ) = ::getKeyValue($index);
if ( defined($err) ) { if ( defined($err) ) {
Log3( $name, 3, ::Log3( $name, 3,
qq{backupToStorage ($name) - unable to read password from file: $err} qq{backupToStorage ($name) - unable to read password from file: $err}
); );
return undef; return;
} }
if ( defined($password) ) { if ( defined($password) ) {
if ( eval "use Digest::MD5;1" ) { if ( eval { use Digest::MD5; 1 } ) {
$key = Digest::MD5::md5_hex( unpack "H*", $key ); $key = Digest::MD5::md5_hex( unpack "H*", $key );
$key .= Digest::MD5::md5_hex($key); $key .= Digest::MD5::md5_hex($key);
} }
my $dec_pwd = ''; my $dec_pwd = '';
for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/g ) ) { for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/xg ) )
{
my $decode = chop($key); my $decode = chop($key);
$dec_pwd .= chr( ord($char) ^ ord($decode) ); $dec_pwd .= chr( ord($char) ^ ord($decode) );
@ -786,8 +770,8 @@ sub ReadPassword {
return $dec_pwd; return $dec_pwd;
} }
else { else {
Log3( $name, 3, qq{backupToStorage ($name) - No password in file} ); ::Log3( $name, 3, qq{backupToStorage ($name) - No password in file} );
return undef; return;
} }
return; return;
@ -798,8 +782,8 @@ sub DeletePassword {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
setKeyValue( $hash->{TYPE} . "_" . $name . "_passwd", undef ); ::setKeyValue( $hash->{TYPE} . "_" . $name . "_passwd", undef );
DoTrigger( $name, 'password remove' ); ::DoTrigger( $name, 'password remove' );
return; return;
} }
@ -809,20 +793,16 @@ sub CheckAttributsForCredentials {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $ncUser = AttrVal( $name, 'bTS_User', 'none' ); my $ncUser = ::AttrVal( $name, 'bTS_User', 'none' );
my $ncPass = ReadPassword( $hash, $name ); my $ncPass = ReadPassword( $hash, $name );
my $ncHost = AttrVal( $name, 'bTS_Host', 'none' ); my $ncHost = ::AttrVal( $name, 'bTS_Host', 'none' );
my $status = 'ready'; my $status = 'ready';
$status = ( $status eq 'ready' $status = (
&& $ncUser eq 'none' $status eq 'ready' && $ncUser eq 'none' ? 'no user credential attribut'
? 'no user credential attribut'
: $status eq 'ready' : $status eq 'ready'
&& $ncHost eq 'none' && $ncHost eq 'none' ? 'no host credential attribut'
? 'no host credential attribut' : $status eq 'ready' && !defined($ncPass) ? 'no password set'
: $status eq 'ready'
&& !defined($ncPass)
? 'no password set'
: $status : $status
); );
@ -837,14 +817,16 @@ sub WriteReadings {
my $decode_json = eval { decode_json($json) }; my $decode_json = eval { decode_json($json) };
if ($@) { if ($@) {
Log3( $name, 2, qq{backupToStorage ($name) - JSON error: $@} ); ::Log3( $name, 2, qq{backupToStorage ($name) - JSON error: $@} );
return; return;
} }
readingsBeginUpdate($hash); ::readingsBeginUpdate($hash);
readingsBulkUpdate( $hash, 'state', 'ready' ); ::readingsBulkUpdate( $hash, 'state', 'ready' );
readingsBulkUpdate( $hash, 'uploadState', $decode_json->{ncUpload} ); ::readingsBulkUpdate( $hash, 'uploadState', $decode_json->{ncUpload} );
readingsEndUpdate( $hash, 1 ); ::readingsEndUpdate( $hash, 1 );
return;
} }
1; 1;