Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
372a8ea361 | |||
dcfebc667b | |||
8e761ed85b | |||
86dc3db975 | |||
260aa0dfbe | |||
37326cb565 | |||
bb46ccab45 | |||
9e3bbcd38f | |||
8dfa21a773 | |||
b0f05d188f | |||
8d4fdf7cea | |||
7d0381f63f | |||
1102232d7e | |||
f4034e4e06 | |||
c00f22acb0 | |||
f150e1f679 | |||
|
6c30fe108b | ||
bdf9052535 | |||
6c715e9b5d | |||
48758f31ab | |||
631be8174f | |||
6d01e89d5b | |||
2692c1bceb | |||
a07173e5a9 | |||
08a76d09a8 | |||
|
da3582fb1e | ||
cbca8d4337 | |||
ec4e32d2fb | |||
|
f0da4a3e70 | ||
c897299103 | |||
4cd8b0ecea | |||
c28ae2e245 | |||
7ae03846bf | |||
1f3ab13b0c | |||
0e1468a032 | |||
37673b2da1 | |||
914c08850e | |||
c408ff36a2 | |||
b02ce13d59 | |||
674e9dce8f | |||
138ee2145f | |||
6dde3f7152 | |||
8460bc4a3e | |||
dc0c072ab5 | |||
16f8bdbce8 | |||
89b9d579c5 | |||
8f8e306e85 | |||
1a0553b92d | |||
d9bb3c4729 | |||
a5084429b0 | |||
18801f0a8f | |||
2aa7914642 | |||
57d95d6141 |
@ -1,8 +1,8 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
# Developed with Kate
|
# Developed with VSCodium and richterger perl plugin
|
||||||
#
|
#
|
||||||
# (c) 2020 Copyright: Marko Oldenburg (fhemsupport@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,32 +29,43 @@
|
|||||||
#
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
package main;
|
package FHEM::backupToStorage;
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use utf8;
|
use utf8;
|
||||||
|
use FHEM::Meta;
|
||||||
|
|
||||||
use FHEM::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
|
||||||
# und davor mit :: getrennt der eigentliche package Name des Modules
|
# und davor mit :: getrennt der eigentliche package Name des Modules
|
||||||
$hash->{SetFn} = \&FHEM::backupToStorage::Set;
|
$hash->{SetFn} = \&FHEM::Services::backupToStorage::Set;
|
||||||
$hash->{DefFn} = \&FHEM::backupToStorage::Define;
|
$hash->{DefFn} = \&FHEM::Services::backupToStorage::Define;
|
||||||
$hash->{NotifyFn} = \&FHEM::backupToStorage::Notify;
|
$hash->{NotifyFn} = \&FHEM::Services::backupToStorage::Notify;
|
||||||
$hash->{UndefFn} = \&FHEM::backupToStorage::Undef;
|
$hash->{UndefFn} = \&FHEM::Services::backupToStorage::Undef;
|
||||||
$hash->{RenameFn} = \&FHEM::backupToStorage::Rename;
|
$hash->{AttrFn} = \&FHEM::Services::backupToStorage::Attr;
|
||||||
$hash->{DeleteFn} = \&FHEM::backupToStorage::Delete;
|
$hash->{RenameFn} = \&FHEM::Services::backupToStorage::Rename;
|
||||||
$hash->{ShutdownFn} = \&FHEM::backupToStorage::Shutdown;
|
$hash->{DeleteFn} = \&FHEM::Services::backupToStorage::Delete;
|
||||||
|
$hash->{ShutdownFn} = \&FHEM::Services::backupToStorage::Shutdown;
|
||||||
$hash->{NotifyOrderPrefix} = '51-'; # Order Nummer für NotifyFn
|
$hash->{NotifyOrderPrefix} = '51-'; # Order Nummer für NotifyFn
|
||||||
$hash->{AttrList} =
|
$hash->{AttrList} =
|
||||||
'bTS_Host '
|
'bTS_Host '
|
||||||
. 'bTS_User '
|
. 'bTS_User '
|
||||||
. 'bTS_Path '
|
. 'bTS_Path '
|
||||||
. 'bTS_Type:Nextcloud';
|
. 'bTS_Proto:http '
|
||||||
|
. 'bTS_Type:Nextcloud,SynologyFileStation '
|
||||||
|
. 'bTS_KeepLastBackups:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 '
|
||||||
|
. 'disable:1 '
|
||||||
|
. 'disabledForIntervals';
|
||||||
$hash->{parseParams} = 1;
|
$hash->{parseParams} = 1;
|
||||||
|
|
||||||
return FHEM::Meta::InitMod( __FILE__, $hash );
|
return FHEM::Meta::InitMod( __FILE__, $hash );
|
||||||
@ -70,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 <name> backupToStorage</code>
|
<code>define <name> backupToStorage</code>
|
||||||
<br>
|
<br>
|
||||||
@ -86,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 </FHEM-Backup></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 </FHEM-Backup>
|
||||||
|
</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>
|
||||||
|
|
||||||
@ -115,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öglichkeit die erstellten Backupdateien vom Modul backup automatisiert auf ein Storage zu laden.<br>
|
Das Modul bietet die Mö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 <name> backupToStorage</code>
|
<code>define <name> backupToStorage</code>
|
||||||
<br>
|
<br>
|
||||||
@ -131,28 +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ür den Login</li>
|
<li><i>bTS_Host</i>
|
||||||
<li>bTS_Path - remote Path wohin das uploadfile soll. z.B. Nextcloud </FHEM-Backup></li>
|
Servername wo sich das Storage drauf befindet
|
||||||
<li>bTS_Type - Storage Type, default ist Nextcloud</li>
|
</li>
|
||||||
|
<a id="backupToStorage-attr-bTS_User"></a>
|
||||||
|
<li><i>bTS_User</i>
|
||||||
|
remote User fü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 </FHEM-Backup>
|
||||||
|
</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>
|
||||||
|
|
||||||
@ -174,15 +223,15 @@ sub backupToStorage_Initialize {
|
|||||||
],
|
],
|
||||||
"release_status": "devepolment",
|
"release_status": "devepolment",
|
||||||
"license": "GPL_2",
|
"license": "GPL_2",
|
||||||
"version": "v1.0.0",
|
"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": {
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
UPD 2020-06-19_08:28:14 6090 FHEM/98_backupToStorage.pm
|
UPD 2023-01-05_20:47:20 7708 FHEM/98_backupToStorage.pm
|
||||||
UPD 2020-06-19_07:55:06 15635 lib/FHEM/backupToStorage.pm
|
UPD 2023-01-16_14:46:22 23390 lib/FHEM/Services/backupToStorage.pm
|
||||||
|
36
hooks/commit-msg
Executable file
36
hooks/commit-msg
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message.
|
||||||
|
# Called by "git commit" with one argument, the name of the file
|
||||||
|
# that has the commit message. The hook should exit with non-zero
|
||||||
|
# status after issuing an appropriate message if it wants to stop the
|
||||||
|
# commit. The hook is allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "commit-msg".
|
||||||
|
|
||||||
|
# Uncomment the below to add a Signed-off-by line to the message.
|
||||||
|
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||||
|
# hook is more suited to it.
|
||||||
|
#
|
||||||
|
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||||
|
|
||||||
|
# This example catches duplicate Signed-off-by lines.
|
||||||
|
|
||||||
|
commit_msg=$(cat "${1:?Missing commit message file}")
|
||||||
|
|
||||||
|
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||||
|
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||||
|
echo >&2 Duplicate Signed-off-by lines.
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! echo "$commit_msg" | grep -Eq "^(build|chore|ci|docs|feat|feat!|fix|perf|refactor|revert|style|test)(\(.+\))?: .*$" ; then
|
||||||
|
|
||||||
|
echo "Invalid commit message"
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Commit message is valid!"
|
18
hooks/post-commit
Executable file
18
hooks/post-commit
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# destination of the final changelog file
|
||||||
|
OUTPUT_FILE=CHANGELOG.md
|
||||||
|
|
||||||
|
# generate the changelog
|
||||||
|
git --no-pager log --no-merges --format="### %s%d%n>%aD%n%n>Author: %aN (%aE)%n%n>Commiter: %cN (%cE)%n%n%b%n%N%n" > $OUTPUT_FILE
|
||||||
|
|
||||||
|
# prevent recursion!
|
||||||
|
# since a 'commit --amend' will trigger the post-commit script again
|
||||||
|
# we have to check if the changelog file has changed or not
|
||||||
|
res=$(git status --porcelain | grep -c ".\$OUTPUT_FILE$")
|
||||||
|
if [ "$res" -gt 0 ]; then
|
||||||
|
git add $OUTPUT_FILE
|
||||||
|
git commit --amend
|
||||||
|
echo "Populated Changelog in $OUTPUT_FILE"
|
||||||
|
fi
|
39
hooks/pre-commit
Executable file
39
hooks/pre-commit
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/perl -w
|
||||||
|
|
||||||
|
use File::Basename;
|
||||||
|
use POSIX qw(strftime);
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
my @filenames = ( 'FHEM/98_backupToStorage.pm',
|
||||||
|
'lib/FHEM/Services/backupToStorage.pm',
|
||||||
|
);
|
||||||
|
|
||||||
|
my $controlsfile = 'controls_backupToStorage.txt';
|
||||||
|
|
||||||
|
open(FH, ">$controlsfile") || return("Can't open $controlsfile: $!");
|
||||||
|
|
||||||
|
for my $filename (@filenames) {
|
||||||
|
my @statOutput = stat($filename);
|
||||||
|
|
||||||
|
if (scalar @statOutput != 13) {
|
||||||
|
printf 'error: stat has unexpected return value for ' . $filename . "\n";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $mtime = $statOutput[9];
|
||||||
|
my $date = POSIX::strftime("%Y-%m-%d", localtime($mtime));
|
||||||
|
my $time = POSIX::strftime("%H:%M:%S", localtime($mtime));
|
||||||
|
my $filetime = $date."_".$time;
|
||||||
|
|
||||||
|
my $filesize = $statOutput[7];
|
||||||
|
|
||||||
|
printf FH 'UPD ' . $filetime . ' ' . $filesize . ' ' .$filename . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
close(FH);
|
||||||
|
|
||||||
|
system("git add $controlsfile");
|
||||||
|
|
||||||
|
print 'Create controls File succesfully' . "\n";
|
||||||
|
|
||||||
|
exit 0;
|
834
lib/FHEM/Services/backupToStorage.pm
Normal file
834
lib/FHEM/Services/backupToStorage.pm
Normal file
@ -0,0 +1,834 @@
|
|||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# Developed with VSCodium and richterger perl plugin
|
||||||
|
#
|
||||||
|
# (c) 2020-2023 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
|
||||||
|
# All rights reserved
|
||||||
|
#
|
||||||
|
# Special thanks goes to:
|
||||||
|
# -
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# This script 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 2 of the License,or
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# The GNU General Public License can be found at
|
||||||
|
# http://www.gnu.org/copyleft/gpl.html.
|
||||||
|
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||||
|
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||||
|
#
|
||||||
|
# This script 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
package FHEM::Services::backupToStorage;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use GPUtils qw(GP_Import);
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
|
||||||
|
# Import from main context
|
||||||
|
GP_Import(
|
||||||
|
qw( init_done
|
||||||
|
defs
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
# try to use JSON::MaybeXS wrapper
|
||||||
|
# for chance of better performance + open code
|
||||||
|
eval {
|
||||||
|
require JSON::MaybeXS;
|
||||||
|
import JSON::MaybeXS qw( decode_json encode_json );
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
|
||||||
|
# try to use JSON wrapper
|
||||||
|
# for chance of better performance
|
||||||
|
eval {
|
||||||
|
# JSON preference order
|
||||||
|
local $ENV{PERL_JSON_BACKEND} =
|
||||||
|
'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP'
|
||||||
|
unless ( defined( $ENV{PERL_JSON_BACKEND} ) );
|
||||||
|
|
||||||
|
require JSON;
|
||||||
|
import JSON qw( decode_json encode_json );
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
|
||||||
|
# In rare cases, Cpanel::JSON::XS may
|
||||||
|
# be installed but JSON|JSON::MaybeXS not ...
|
||||||
|
eval {
|
||||||
|
require Cpanel::JSON::XS;
|
||||||
|
import Cpanel::JSON::XS qw(decode_json encode_json);
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
|
||||||
|
# In rare cases, JSON::XS may
|
||||||
|
# be installed but JSON not ...
|
||||||
|
eval {
|
||||||
|
require JSON::XS;
|
||||||
|
import JSON::XS qw(decode_json encode_json);
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
|
||||||
|
# Fallback to built-in JSON which SHOULD
|
||||||
|
# be available since 5.014 ...
|
||||||
|
eval {
|
||||||
|
require JSON::PP;
|
||||||
|
import JSON::PP qw(decode_json encode_json);
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
|
||||||
|
# Fallback to JSON::backportPP in really rare cases
|
||||||
|
require JSON::backportPP;
|
||||||
|
import JSON::backportPP qw(decode_json encode_json);
|
||||||
|
1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sub Define {
|
||||||
|
use version 0.60;
|
||||||
|
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $aArg = shift // return;
|
||||||
|
|
||||||
|
my $version;
|
||||||
|
|
||||||
|
return $@ unless ( FHEM::Meta::SetInternals($hash) );
|
||||||
|
|
||||||
|
$version = FHEM::Meta::Get( $hash, 'version' );
|
||||||
|
our $VERSION = $version;
|
||||||
|
|
||||||
|
return q{only one backupToStorage instance allowed}
|
||||||
|
if ( ::devspec2array('TYPE=backupToStorage') > 1 )
|
||||||
|
; # es wird geprüft ob bereits eine Instanz unseres Modules existiert,wenn ja wird abgebrochen
|
||||||
|
return q{too few parameters: define <name> backupToStorage}
|
||||||
|
if ( scalar( @{$aArg} ) != 2 );
|
||||||
|
|
||||||
|
my $name = shift @$aArg;
|
||||||
|
$hash->{VERSION} = version->parse($VERSION)->normal;
|
||||||
|
$hash->{NOTIFYDEV} = 'global,' . $name;
|
||||||
|
$hash->{STORAGETYPE} = ::AttrVal( $name, 'bTS_Type', 'Nextcloud' );
|
||||||
|
|
||||||
|
::Log3( $name, 3, qq{backupToStorage ($name) - defined} );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Undef {
|
||||||
|
my $hash = shift;
|
||||||
|
my $name = shift;
|
||||||
|
|
||||||
|
::Log3( $name, 3, q{qbackupToStorage ($name) - delete device $name} );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Delete {
|
||||||
|
my $hash = shift;
|
||||||
|
my $name = shift;
|
||||||
|
|
||||||
|
HttpUtils_Close( $hash->{helper}->{HttpUtilsParam} )
|
||||||
|
if ( defined( $hash->{helper}->{HttpUtilsParam} ) );
|
||||||
|
DeletePassword($hash);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Shutdown {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
HttpUtils_Close( $hash->{helper}->{HttpUtilsParam} )
|
||||||
|
if ( defined( $hash->{helper}->{HttpUtilsParam} ) );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Notify {
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $dev = shift // return;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $devname = $dev->{NAME};
|
||||||
|
my $devtype = $dev->{TYPE};
|
||||||
|
my $events = ::deviceEvents( $dev, 1 );
|
||||||
|
|
||||||
|
_CheckIsDisabledAfterSetAttr($hash)
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
grep { /^DELETEATTR.$name.(disable|disabledForIntervals)$/x }
|
||||||
|
@{$events}
|
||||||
|
or grep { /^ATTR.$name.(disable|disabledForIntervals).\S+$/x }
|
||||||
|
@{$events}
|
||||||
|
)
|
||||||
|
&& $devname eq 'global'
|
||||||
|
&& $init_done
|
||||||
|
)
|
||||||
|
|| $devname eq $name
|
||||||
|
);
|
||||||
|
|
||||||
|
return if ( !$events
|
||||||
|
|| ::IsDisabled($name) );
|
||||||
|
|
||||||
|
::Log3( $name, 4,
|
||||||
|
qq{backupToStorage ($name) - Devname: $devname Name: $name Notify: } );
|
||||||
|
|
||||||
|
PushToStorage($hash)
|
||||||
|
if ( ( grep { /^backup.done(.+)?$/x } @{$events} )
|
||||||
|
&& $devname eq 'global'
|
||||||
|
&& $init_done );
|
||||||
|
|
||||||
|
CheckAttributsForCredentials($hash)
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
grep { /^DELETEATTR.$name.(bTS_Host|bTS_User)$/x }
|
||||||
|
@{$events}
|
||||||
|
or grep { /^ATTR.$name.(bTS_Host|bTS_User).\S+$/x }
|
||||||
|
@{$events}
|
||||||
|
)
|
||||||
|
&& $devname eq 'global'
|
||||||
|
)
|
||||||
|
|| (
|
||||||
|
(
|
||||||
|
$devname eq $name && grep { /^password.(add|remove)$/x }
|
||||||
|
@{$events}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
&& $init_done
|
||||||
|
);
|
||||||
|
|
||||||
|
::readingsSingleUpdate(
|
||||||
|
$hash, 'state',
|
||||||
|
(
|
||||||
|
(
|
||||||
|
::AttrVal( $name, 'bTS_Host', 'none' ) eq 'none'
|
||||||
|
|| ::AttrVal( $name, 'bTS_User', 'none' ) eq 'none'
|
||||||
|
|| !defined( ReadPassword( $hash, $name ) )
|
||||||
|
)
|
||||||
|
? 'please set storage account credentials first'
|
||||||
|
: 'ready'
|
||||||
|
),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
( grep { /^DEFINED.$name$/x } @{$events} )
|
||||||
|
&& $devname eq 'global'
|
||||||
|
&& $init_done
|
||||||
|
)
|
||||||
|
|| ( grep { /^INITIALIZED$/x } @{$events}
|
||||||
|
or grep { /^REREADCFG$/x } @{$events}
|
||||||
|
or grep { /^MODIFIED.$name$/x } @{$events} )
|
||||||
|
&& $devname eq 'global'
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Set {
|
||||||
|
my $hash = shift // return;
|
||||||
|
my $aArg = shift // return;
|
||||||
|
|
||||||
|
my $name = shift @$aArg;
|
||||||
|
my $cmd = shift @$aArg
|
||||||
|
// return qq{set "$name" needs at least one argument};
|
||||||
|
|
||||||
|
if ( lc $cmd eq 'addpassword' ) {
|
||||||
|
return q{please set Attribut bTS_User first}
|
||||||
|
if ( ::AttrVal( $name, 'bTS_User', 'none' ) eq 'none' );
|
||||||
|
return qq{usage: "$cmd" <password>}
|
||||||
|
if ( scalar( @{$aArg} ) != 1 );
|
||||||
|
|
||||||
|
StorePassword( $hash, $name, $aArg->[0] );
|
||||||
|
}
|
||||||
|
elsif ( lc $cmd eq 'deletepassword' ) {
|
||||||
|
return qq{usage: $cmd}
|
||||||
|
if ( scalar( @{$aArg} ) != 0 );
|
||||||
|
|
||||||
|
DeletePassword($hash);
|
||||||
|
}
|
||||||
|
elsif ( lc $cmd eq 'active' ) {
|
||||||
|
return qq{usage: $cmd}
|
||||||
|
if ( scalar( @{$aArg} ) != 0 );
|
||||||
|
|
||||||
|
::readingsSingleUpdate( $hash, 'state', 'ready', 1 );
|
||||||
|
}
|
||||||
|
elsif ( lc $cmd eq 'inactive' ) {
|
||||||
|
return qq{usage: $cmd}
|
||||||
|
if ( scalar( @{$aArg} ) != 0 );
|
||||||
|
|
||||||
|
::readingsSingleUpdate( $hash, 'state', $cmd, 1 );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my $list = 'active:noArg inactive:noArg';
|
||||||
|
$list .= (
|
||||||
|
defined( ReadPassword( $hash, $name ) )
|
||||||
|
? ' deletepassword:noArg'
|
||||||
|
: ' addpassword'
|
||||||
|
);
|
||||||
|
|
||||||
|
return qq{Unknown argument "$cmd", choose one of $list};
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Attr {
|
||||||
|
my $cmd = shift;
|
||||||
|
my $name = shift;
|
||||||
|
|
||||||
|
my $hash = $defs{$name};
|
||||||
|
my $attrName = shift;
|
||||||
|
my $attrVal = shift;
|
||||||
|
|
||||||
|
if ( $attrName eq 'disable'
|
||||||
|
|| $attrName eq 'disabledForIntervals' )
|
||||||
|
{
|
||||||
|
|
||||||
|
if ( $cmd eq 'set' ) {
|
||||||
|
if ( $attrName eq 'disabledForIntervals' ) {
|
||||||
|
return
|
||||||
|
'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?)+$/x );
|
||||||
|
::Log3( $name, 3,
|
||||||
|
qq{backupToStorage ($name) - disabledForIntervals} );
|
||||||
|
}
|
||||||
|
elsif ( $attrName eq 'disable' ) {
|
||||||
|
::Log3( $name, 3, qq{backupToStorage ($name) - disabled} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::InternalTimer(
|
||||||
|
::gettimeofday() + 1,
|
||||||
|
'FHEM::Services::backupToStorage::_CheckIsDisabledAfterSetAttr',
|
||||||
|
$hash, 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
elsif ( $attrName eq 'bTS_Type' ) {
|
||||||
|
::InternalTimer(
|
||||||
|
::gettimeofday() + 1,
|
||||||
|
sub {
|
||||||
|
$hash->{STORAGETYPE} =
|
||||||
|
::AttrVal( $name, 'bTS_Type', 'Nextcloud' );
|
||||||
|
},
|
||||||
|
$hash,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _CheckIsDisabledAfterSetAttr {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $state = (
|
||||||
|
::IsDisabled($name)
|
||||||
|
? 'inactive'
|
||||||
|
: 'ready'
|
||||||
|
);
|
||||||
|
|
||||||
|
::Log3( $name, 3,
|
||||||
|
qq{backupToStorage ($name) - _CheckIsDisabledAfterSetAttr} );
|
||||||
|
|
||||||
|
::readingsSingleUpdate( $hash, 'state', $state, 1 )
|
||||||
|
if ( ::ReadingsVal( $name, 'state', 'ready' ) ne $state );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub Rename {
|
||||||
|
my $new = shift;
|
||||||
|
my $old = shift;
|
||||||
|
|
||||||
|
my $hash = $defs{$new};
|
||||||
|
|
||||||
|
StorePassword( $hash, $new, ReadPassword( $hash, $old ) );
|
||||||
|
::setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub PushToStorage {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
::Log3( $name, 4, qq{backupToStorage ($name) - push to storage function} );
|
||||||
|
|
||||||
|
return ::Log3( $name, 4,
|
||||||
|
qq{backupToStorage ($name) - fhemBackupFile Reading to old} )
|
||||||
|
if ( ::ReadingsAge( $name, 'fhemBackupFile', 1 ) > 3600 );
|
||||||
|
|
||||||
|
::Log3( $name, 4, qq{backupToStorage ($name) - after readings age return} );
|
||||||
|
|
||||||
|
if ( $hash->{STORAGETYPE} eq 'SynologyFileStation' ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
require SubProcess;
|
||||||
|
my $subprocess = SubProcess->new( { onRun => \&FileUpload } );
|
||||||
|
|
||||||
|
my $backupFile = ::ReadingsVal( $name, 'fhemBackupFile', 'none' );
|
||||||
|
|
||||||
|
my @fileNameAtStorage_array = split( '/', $backupFile );
|
||||||
|
my $fileNameAtStorage =
|
||||||
|
$fileNameAtStorage_array[$#fileNameAtStorage_array];
|
||||||
|
|
||||||
|
$subprocess->{curl} = qx(which curl);
|
||||||
|
chomp( $subprocess->{curl} );
|
||||||
|
$subprocess->{fhemhost} = qx(hostname -f);
|
||||||
|
chomp( $subprocess->{fhemhost} );
|
||||||
|
$subprocess->{type} = $hash->{STORAGETYPE};
|
||||||
|
$subprocess->{host} = ::AttrVal( $name, 'bTS_Host', '' );
|
||||||
|
$subprocess->{user} = ::AttrVal( $name, 'bTS_User', '' );
|
||||||
|
$subprocess->{pass} = ReadPassword( $hash, $name );
|
||||||
|
$subprocess->{path} = ::AttrVal( $name, 'bTS_Path', '' );
|
||||||
|
$subprocess->{backupfile} = $backupFile;
|
||||||
|
$subprocess->{fileNameAtStorage} = $fileNameAtStorage;
|
||||||
|
$subprocess->{proto} = ::AttrVal( $name, 'bTS_Proto', 'https' );
|
||||||
|
$subprocess->{loglevel} = ::AttrVal( $name, 'verbose', 3 );
|
||||||
|
|
||||||
|
my $pid = $subprocess->run();
|
||||||
|
|
||||||
|
::readingsSingleUpdate( $hash, 'state', ' file upload in progress', 1 );
|
||||||
|
|
||||||
|
if ( !defined($pid) ) {
|
||||||
|
::Log3( $name, 1,
|
||||||
|
qq{backupToStorage ($name) - Cannot execute command asynchronously}
|
||||||
|
);
|
||||||
|
|
||||||
|
CleanSubprocess($hash);
|
||||||
|
::readingsSingleUpdate( $hash, 'state',
|
||||||
|
'Cannot execute command asynchronously', 1 );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
::Log3( $name, 4,
|
||||||
|
qq{backupToStorage ($name) - execute command asynchronously (PID="$pid")}
|
||||||
|
);
|
||||||
|
|
||||||
|
$hash->{".fhem"}{subprocess} = $subprocess;
|
||||||
|
|
||||||
|
::InternalTimer( ::gettimeofday() + 1,
|
||||||
|
"FHEM::Services::backupToStorage::PollChild", $hash );
|
||||||
|
}
|
||||||
|
|
||||||
|
::Log3( $hash, 4,
|
||||||
|
qq{backupToStorage ($name) - control passed back to main loop.} );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub KeepLastN {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
::Log3( $name, 4,
|
||||||
|
qq{backupToStorage ($name) - Keep Last N at Storage function} );
|
||||||
|
|
||||||
|
if ( $hash->{STORAGETYPE} eq 'SynologyFileStation' ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
require SubProcess;
|
||||||
|
my $subprocess = SubProcess->new( { onRun => \&CleanUp } );
|
||||||
|
|
||||||
|
my $backupFile = ::ReadingsVal( $name, 'fhemBackupFile', 'none' );
|
||||||
|
|
||||||
|
my @fileNameAtStorage_array = split( '/', $backupFile );
|
||||||
|
my $fileNameAtStorage =
|
||||||
|
$fileNameAtStorage_array[$#fileNameAtStorage_array];
|
||||||
|
|
||||||
|
$subprocess->{curl} = qx(which curl);
|
||||||
|
chomp( $subprocess->{curl} );
|
||||||
|
$subprocess->{type} = $hash->{STORAGETYPE};
|
||||||
|
$subprocess->{host} = ::AttrVal( $name, 'bTS_Host', '' );
|
||||||
|
$subprocess->{user} = ::AttrVal( $name, 'bTS_User', '' );
|
||||||
|
$subprocess->{pass} = ReadPassword( $hash, $name );
|
||||||
|
$subprocess->{path} = ::AttrVal( $name, 'bTS_Path', '' );
|
||||||
|
$subprocess->{fileNameAtStorage} = $fileNameAtStorage;
|
||||||
|
$subprocess->{proto} = ::AttrVal( $name, 'bTS_Proto', 'https' );
|
||||||
|
$subprocess->{loglevel} = ::AttrVal( $name, 'verbose', 3 );
|
||||||
|
$subprocess->{keeplastn} = ::AttrVal( $name, 'bTS_KeepLastBackups', 5 );
|
||||||
|
|
||||||
|
my $pid = $subprocess->run();
|
||||||
|
|
||||||
|
::readingsSingleUpdate( $hash, 'state',
|
||||||
|
' clean up pass last N in progress', 1 );
|
||||||
|
|
||||||
|
if ( !defined($pid) ) {
|
||||||
|
::Log3( $name, 1,
|
||||||
|
qq{backupToStorage ($name) - Cannot execute command asynchronously}
|
||||||
|
);
|
||||||
|
|
||||||
|
CleanSubprocess($hash);
|
||||||
|
::readingsSingleUpdate( $hash, 'state',
|
||||||
|
'Cannot execute command asynchronously', 1 );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
::Log3( $name, 4,
|
||||||
|
qq{backupToStorage ($name) - execute command asynchronously (PID="$pid")}
|
||||||
|
);
|
||||||
|
|
||||||
|
$hash->{".fhem"}{subprocess} = $subprocess;
|
||||||
|
|
||||||
|
::InternalTimer( ::gettimeofday() + 1,
|
||||||
|
"FHEM::Services::backupToStorage::PollChild", $hash );
|
||||||
|
}
|
||||||
|
|
||||||
|
::Log3( $hash, 4,
|
||||||
|
qq{backupToStorage ($name) - control passed back to main loop.} );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub PollChild {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
if ( defined( $hash->{".fhem"}{subprocess} ) ) {
|
||||||
|
my $subprocess = $hash->{".fhem"}{subprocess};
|
||||||
|
my $json = $subprocess->readFromChild();
|
||||||
|
|
||||||
|
if ( !defined($json) ) {
|
||||||
|
::Log3( $name, 5,
|
||||||
|
qq{backupToStorage ($name) - still waiting ($subprocess->{lasterror}).}
|
||||||
|
);
|
||||||
|
|
||||||
|
::InternalTimer( ::gettimeofday() + 1,
|
||||||
|
"FHEM::Services::backupToStorage::PollChild", $hash );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
::Log3( $name, 4,
|
||||||
|
qq{backupToStorage ($name) - got result from asynchronous parsing: $json}
|
||||||
|
);
|
||||||
|
|
||||||
|
$subprocess->wait();
|
||||||
|
::Log3( $name, 4,
|
||||||
|
qq{backupToStorage ($name) - asynchronous finished.} );
|
||||||
|
|
||||||
|
CleanSubprocess($hash);
|
||||||
|
WriteReadings( $hash, $json );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
######################################
|
||||||
|
# Begin Childprozess
|
||||||
|
######################################
|
||||||
|
sub FileUpload {
|
||||||
|
my $subprocess = shift;
|
||||||
|
my $response = {};
|
||||||
|
|
||||||
|
if ( $subprocess->{type} eq 'Nextcloud' ) {
|
||||||
|
my ( $returnString, $returnCode ) = ExecuteNCupload($subprocess);
|
||||||
|
|
||||||
|
print 'backupToStorage File Upload - FileUpload Nextcloud, returnCode: '
|
||||||
|
. $returnCode
|
||||||
|
. ' , returnString: '
|
||||||
|
. $returnString . "\n"
|
||||||
|
if ( $subprocess->{loglevel} > 4 );
|
||||||
|
|
||||||
|
if ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm
|
||||||
|
and $returnString =~ /\s\s<o:hint xmlns:o="o:">(.*)<\/o:hint>/xm )
|
||||||
|
{
|
||||||
|
$response->{ncUpload} = $1;
|
||||||
|
}
|
||||||
|
elsif ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm ) {
|
||||||
|
$response->{ncUpload} = 'upload successfully';
|
||||||
|
}
|
||||||
|
elsif ( $returnString =~ /(curl:\s.*)/x ) {
|
||||||
|
$response->{ncUpload} = $1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$response->{ncUpload} = 'unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $json = eval { encode_json($response) };
|
||||||
|
if ($@) {
|
||||||
|
print 'backupToStorage File Upload backupToStorage - JSON error: $@'
|
||||||
|
. "\n";
|
||||||
|
$json = '{"jsonerror":"$@"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
$subprocess->writeToParent($json);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ExecuteNCupload {
|
||||||
|
my $subprocess = shift;
|
||||||
|
|
||||||
|
my $command = $subprocess->{curl};
|
||||||
|
$command .= ' -k -X PUT -u ';
|
||||||
|
$command .= $subprocess->{user} . ':' . $subprocess->{pass};
|
||||||
|
$command .= ' -T ' . $subprocess->{backupfile};
|
||||||
|
$command .= ' "' . $subprocess->{proto} . '://';
|
||||||
|
$command .= $subprocess->{host};
|
||||||
|
$command .= '/remote.php/dav/files/';
|
||||||
|
$command .= $subprocess->{user};
|
||||||
|
$command .= $subprocess->{path};
|
||||||
|
$command .= '/';
|
||||||
|
$command .=
|
||||||
|
$subprocess->{fhemhost} . '-' . $subprocess->{fileNameAtStorage};
|
||||||
|
$command .= '"';
|
||||||
|
|
||||||
|
return ExecuteCommand($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub CleanUp {
|
||||||
|
my $subprocess = shift;
|
||||||
|
my $response = {};
|
||||||
|
|
||||||
|
if ( $subprocess->{type} eq 'Nextcloud' ) {
|
||||||
|
my ( $returnString, $returnCode ) = ExecuteCleanUp($subprocess);
|
||||||
|
|
||||||
|
print 'backupToStorage File Upload - FileUpload Nextcloud, returnCode: '
|
||||||
|
. $returnCode
|
||||||
|
. ' , returnString: '
|
||||||
|
. $returnString . "\n"
|
||||||
|
if ( $subprocess->{loglevel} > 4 );
|
||||||
|
|
||||||
|
if ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm
|
||||||
|
and $returnString =~ /\s\s<o:hint xmlns:o="o:">(.*)<\/o:hint>/xm )
|
||||||
|
{
|
||||||
|
$response->{ncUpload} = $1;
|
||||||
|
}
|
||||||
|
elsif ( $returnString =~ /100\s\s?[0-9].*\s100\s\s?[0-9].*/xm ) {
|
||||||
|
$response->{ncUpload} = 'upload successfully';
|
||||||
|
}
|
||||||
|
elsif ( $returnString =~ /(curl:\s.*)/x ) {
|
||||||
|
$response->{ncUpload} = $1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$response->{ncUpload} = 'unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $json = eval { encode_json($response) };
|
||||||
|
if ($@) {
|
||||||
|
print 'backupToStorage File Upload backupToStorage - JSON error: $@'
|
||||||
|
. "\n";
|
||||||
|
$json = '{"jsonerror":"$@"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
$subprocess->writeToParent($json);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ExecuteNCfetchFileList {
|
||||||
|
my $subprocess = shift;
|
||||||
|
|
||||||
|
my $command = $subprocess->{curl};
|
||||||
|
$command .= ' -k -X PROPFIND -u ';
|
||||||
|
$command .= $subprocess->{user} . ':' . $subprocess->{pass};
|
||||||
|
$command .= ' "' . $subprocess->{proto} . '://';
|
||||||
|
$command .= $subprocess->{host};
|
||||||
|
$command .= '/remote.php/dav/files/';
|
||||||
|
$command .= $subprocess->{user};
|
||||||
|
$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>\'';
|
||||||
|
|
||||||
|
return ExecuteCommand($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ExecuteNCremoveFile {
|
||||||
|
my $subprocess = shift;
|
||||||
|
|
||||||
|
my $command = $subprocess->{curl};
|
||||||
|
$command .= ' -k -X DELETE -u ';
|
||||||
|
$command .= $subprocess->{user} . ':' . $subprocess->{pass};
|
||||||
|
$command .= ' "' . $subprocess->{proto} . '://';
|
||||||
|
$command .= $subprocess->{host};
|
||||||
|
$command .= '/remote.php/dav/files/';
|
||||||
|
$command .= $subprocess->{user};
|
||||||
|
$command .= $subprocess->{path};
|
||||||
|
$command .= '/';
|
||||||
|
$command .= $subprocess->{fileNameAtStorage};
|
||||||
|
$command .= '"';
|
||||||
|
|
||||||
|
return ExecuteCommand($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ExecuteCommand {
|
||||||
|
my @options = @_;
|
||||||
|
my $command = join q{ }, @options;
|
||||||
|
return ( $_ = qx{$command 2>&1}, $? >> 8 );
|
||||||
|
}
|
||||||
|
|
||||||
|
######################################
|
||||||
|
# End Childprozess
|
||||||
|
######################################
|
||||||
|
|
||||||
|
sub CleanSubprocess {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
delete( $hash->{".fhem"}{subprocess} );
|
||||||
|
::Log3( $name, 4, qq{backupToStorage ($name) - clean Subprocess} );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub StorePassword {
|
||||||
|
my $hash = shift;
|
||||||
|
my $name = shift;
|
||||||
|
my $password = shift;
|
||||||
|
|
||||||
|
my $index = $hash->{TYPE} . "_" . $name . "_passwd";
|
||||||
|
my $key = ::getUniqueId() . $index;
|
||||||
|
my $enc_pwd = "";
|
||||||
|
|
||||||
|
if ( eval { use Digest::MD5; 1 } ) {
|
||||||
|
|
||||||
|
$key = Digest::MD5::md5_hex( unpack "H*", $key );
|
||||||
|
$key .= Digest::MD5::md5_hex($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $char ( split //, $password ) {
|
||||||
|
|
||||||
|
my $encode = chop($key);
|
||||||
|
$enc_pwd .= sprintf( "%.2x", ord($char) ^ ord($encode) );
|
||||||
|
$key = $encode . $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $err = ::setKeyValue( $index, $enc_pwd );
|
||||||
|
::DoTrigger( $name, 'password add' );
|
||||||
|
|
||||||
|
return qq{error while saving the password - $err}
|
||||||
|
if ( defined($err) );
|
||||||
|
|
||||||
|
return q{password successfully saved};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ReadPassword {
|
||||||
|
my $hash = shift;
|
||||||
|
my $name = shift;
|
||||||
|
|
||||||
|
my $index = $hash->{TYPE} . "_" . $name . "_passwd";
|
||||||
|
my $key = ::getUniqueId() . $index;
|
||||||
|
my ( $password, $err );
|
||||||
|
|
||||||
|
::Log3( $name, 4, qq{backupToStorage ($name) - Read password from file} );
|
||||||
|
|
||||||
|
( $err, $password ) = ::getKeyValue($index);
|
||||||
|
|
||||||
|
if ( defined($err) ) {
|
||||||
|
|
||||||
|
::Log3( $name, 3,
|
||||||
|
qq{backupToStorage ($name) - unable to read password from file: $err}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( defined($password) ) {
|
||||||
|
if ( eval { use Digest::MD5; 1 } ) {
|
||||||
|
$key = Digest::MD5::md5_hex( unpack "H*", $key );
|
||||||
|
$key .= Digest::MD5::md5_hex($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $dec_pwd = '';
|
||||||
|
|
||||||
|
for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/xg ) )
|
||||||
|
{
|
||||||
|
|
||||||
|
my $decode = chop($key);
|
||||||
|
$dec_pwd .= chr( ord($char) ^ ord($decode) );
|
||||||
|
$key = $decode . $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dec_pwd;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
::Log3( $name, 3, qq{backupToStorage ($name) - No password in file} );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub DeletePassword {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
::setKeyValue( $hash->{TYPE} . "_" . $name . "_passwd", undef );
|
||||||
|
::DoTrigger( $name, 'password remove' );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub CheckAttributsForCredentials {
|
||||||
|
my $hash = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
my $ncUser = ::AttrVal( $name, 'bTS_User', 'none' );
|
||||||
|
my $ncPass = ReadPassword( $hash, $name );
|
||||||
|
my $ncHost = ::AttrVal( $name, 'bTS_Host', 'none' );
|
||||||
|
my $status = 'ready';
|
||||||
|
|
||||||
|
$status = (
|
||||||
|
$status eq 'ready' && $ncUser eq 'none' ? 'no user credential attribut'
|
||||||
|
: $status eq 'ready'
|
||||||
|
&& $ncHost eq 'none' ? 'no host credential attribut'
|
||||||
|
: $status eq 'ready' && !defined($ncPass) ? 'no password set'
|
||||||
|
: $status
|
||||||
|
);
|
||||||
|
|
||||||
|
return ::readingsSingleUpdate( $hash, 'state', $status, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub WriteReadings {
|
||||||
|
my $hash = shift;
|
||||||
|
my $json = shift;
|
||||||
|
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
my $decode_json = eval { decode_json($json) };
|
||||||
|
if ($@) {
|
||||||
|
::Log3( $name, 2, qq{backupToStorage ($name) - JSON error: $@} );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
::readingsBeginUpdate($hash);
|
||||||
|
::readingsBulkUpdate( $hash, 'state', 'ready' );
|
||||||
|
::readingsBulkUpdate( $hash, 'uploadState', $decode_json->{ncUpload} );
|
||||||
|
::readingsEndUpdate( $hash, 1 );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
@ -1,596 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
#
|
|
||||||
# Developed with Kate
|
|
||||||
#
|
|
||||||
# (c) 2020 Copyright: Marko Oldenburg (fhemsupport@cooltux.net)
|
|
||||||
# All rights reserved
|
|
||||||
#
|
|
||||||
# Special thanks goes to:
|
|
||||||
# -
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# This script 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 2 of the License,or
|
|
||||||
# any later version.
|
|
||||||
#
|
|
||||||
# The GNU General Public License can be found at
|
|
||||||
# http://www.gnu.org/copyleft/gpl.html.
|
|
||||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
|
||||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
|
||||||
#
|
|
||||||
# This script 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# $Id$
|
|
||||||
#
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
package FHEM::backupToStorage;
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use utf8;
|
|
||||||
|
|
||||||
use GPUtils qw(GP_Import);
|
|
||||||
|
|
||||||
use Data::Dumper; #only for Debugging
|
|
||||||
|
|
||||||
# try to use JSON::MaybeXS wrapper
|
|
||||||
# for chance of better performance + open code
|
|
||||||
eval {
|
|
||||||
require JSON::MaybeXS;
|
|
||||||
import JSON::MaybeXS qw( decode_json encode_json );
|
|
||||||
1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($@) {
|
|
||||||
$@ = undef;
|
|
||||||
|
|
||||||
# try to use JSON wrapper
|
|
||||||
# for chance of better performance
|
|
||||||
eval {
|
|
||||||
|
|
||||||
# JSON preference order
|
|
||||||
local $ENV{PERL_JSON_BACKEND} =
|
|
||||||
'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP'
|
|
||||||
unless ( defined( $ENV{PERL_JSON_BACKEND} ) );
|
|
||||||
|
|
||||||
require JSON;
|
|
||||||
import JSON qw( decode_json encode_json );
|
|
||||||
1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($@) {
|
|
||||||
$@ = undef;
|
|
||||||
|
|
||||||
# In rare cases, Cpanel::JSON::XS may
|
|
||||||
# be installed but JSON|JSON::MaybeXS not ...
|
|
||||||
eval {
|
|
||||||
require Cpanel::JSON::XS;
|
|
||||||
import Cpanel::JSON::XS qw(decode_json encode_json);
|
|
||||||
1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($@) {
|
|
||||||
$@ = undef;
|
|
||||||
|
|
||||||
# In rare cases, JSON::XS may
|
|
||||||
# be installed but JSON not ...
|
|
||||||
eval {
|
|
||||||
require JSON::XS;
|
|
||||||
import JSON::XS qw(decode_json encode_json);
|
|
||||||
1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($@) {
|
|
||||||
$@ = undef;
|
|
||||||
|
|
||||||
# Fallback to built-in JSON which SHOULD
|
|
||||||
# be available since 5.014 ...
|
|
||||||
eval {
|
|
||||||
require JSON::PP;
|
|
||||||
import JSON::PP qw(decode_json encode_json);
|
|
||||||
1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($@) {
|
|
||||||
$@ = undef;
|
|
||||||
|
|
||||||
# Fallback to JSON::backportPP in really rare cases
|
|
||||||
require JSON::backportPP;
|
|
||||||
import JSON::backportPP qw(decode_json encode_json);
|
|
||||||
1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
## Import der FHEM Funktionen
|
|
||||||
#-- Run before package compilation
|
|
||||||
BEGIN {
|
|
||||||
|
|
||||||
# Import from main context
|
|
||||||
GP_Import(
|
|
||||||
qw(
|
|
||||||
readingsSingleUpdate
|
|
||||||
readingsBulkUpdate
|
|
||||||
readingsBeginUpdate
|
|
||||||
readingsEndUpdate
|
|
||||||
ReadingsVal
|
|
||||||
gettimeofday
|
|
||||||
InternalTimer
|
|
||||||
defs
|
|
||||||
modules
|
|
||||||
setKeyValue
|
|
||||||
getKeyValue
|
|
||||||
getUniqueId
|
|
||||||
Log3
|
|
||||||
CommandAttr
|
|
||||||
attr
|
|
||||||
AttrVal
|
|
||||||
deviceEvents
|
|
||||||
init_done
|
|
||||||
devspec2array
|
|
||||||
DoTrigger
|
|
||||||
HttpUtils_NonblockingGet)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Define {
|
|
||||||
my $hash = shift // return;
|
|
||||||
my $aArg = shift // return;
|
|
||||||
|
|
||||||
return $@ unless ( FHEM::Meta::SetInternals($hash) );
|
|
||||||
use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
|
|
||||||
|
|
||||||
return 'only one backupToStorage instance allowed'
|
|
||||||
if ( devspec2array('TYPE=backupToStorage') > 1 )
|
|
||||||
; # es wird geprüft ob bereits eine Instanz unseres Modules existiert,wenn ja wird abgebrochen
|
|
||||||
return 'too few parameters: define <name> backupToStorage'
|
|
||||||
if ( scalar( @{$aArg} ) != 2 );
|
|
||||||
|
|
||||||
my $name = shift @$aArg;
|
|
||||||
$hash->{VERSION} = version->parse($VERSION)->normal;
|
|
||||||
$hash->{NOTIFYDEV} = 'global,' . $name;
|
|
||||||
$hash->{STORAGETYPE} = AttrVal( $name, 'bTS_Type', 'Nextcloud' );
|
|
||||||
|
|
||||||
Log3( $name, 3, "backupToStorage ($name) - defined" );
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Undef {
|
|
||||||
my $hash = shift;
|
|
||||||
my $name = shift;
|
|
||||||
|
|
||||||
Log3( $name, 3, "backupToStorage ($name) - delete device $name" );
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Delete {
|
|
||||||
my $hash = shift;
|
|
||||||
my $name = shift;
|
|
||||||
|
|
||||||
HttpUtils_Close( $hash->{helper}->{HttpUtilsParam} )
|
|
||||||
if ( defined($hash->{helper}->{HttpUtilsParam}) );
|
|
||||||
DeletePassword($hash);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Shutdown {
|
|
||||||
my $hash = shift;
|
|
||||||
|
|
||||||
HttpUtils_Close( $hash->{helper}->{HttpUtilsParam} )
|
|
||||||
if ( defined($hash->{helper}->{HttpUtilsParam}) );
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Notify {
|
|
||||||
my $hash = shift // return;
|
|
||||||
my $dev = shift // return;
|
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
my $devname = $dev->{NAME};
|
|
||||||
my $devtype = $dev->{TYPE};
|
|
||||||
my $events = deviceEvents( $dev, 1 );
|
|
||||||
return if ( !$events );
|
|
||||||
|
|
||||||
Log3( $name, 4,
|
|
||||||
"AutoShuttersControl ($name) - Devname: "
|
|
||||||
. $devname
|
|
||||||
. " Name: "
|
|
||||||
. $name
|
|
||||||
. " Notify: "
|
|
||||||
. Dumper $events); # mit Dumper
|
|
||||||
|
|
||||||
PushToStorage($hash)
|
|
||||||
if ( ( grep m{^backup.done(.+)?$}xms, @{$events} )
|
|
||||||
&& $devname eq 'global'
|
|
||||||
&& $init_done );
|
|
||||||
|
|
||||||
CheckAttributsForCredentials($hash)
|
|
||||||
if (
|
|
||||||
(
|
|
||||||
(
|
|
||||||
(
|
|
||||||
grep m{^DELETEATTR.$name.(bTS_Host|bTS_User)$}xms,
|
|
||||||
@{$events}
|
|
||||||
or grep m{^ATTR.$name.(bTS_Host|bTS_User).\S+$}xms,
|
|
||||||
@{$events}
|
|
||||||
)
|
|
||||||
&& $devname eq 'global'
|
|
||||||
)
|
|
||||||
|| (
|
|
||||||
(
|
|
||||||
$devname eq $name && grep m{^password.(add|remove)$}xms,
|
|
||||||
@{$events}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
&& $init_done
|
|
||||||
);
|
|
||||||
|
|
||||||
readingsSingleUpdate(
|
|
||||||
$hash, 'state',
|
|
||||||
(
|
|
||||||
(
|
|
||||||
AttrVal( $name, 'bTS_Host', 'none' ) eq 'none'
|
|
||||||
|| AttrVal( $name, 'bTS_User', 'none' ) eq 'none'
|
|
||||||
|| !defined( ReadPassword( $hash, $name ) )
|
|
||||||
)
|
|
||||||
? 'please set storage account credentials first'
|
|
||||||
: 'ready'
|
|
||||||
),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
(
|
|
||||||
( grep m{^DEFINED.$name$}xms, @{$events} )
|
|
||||||
&& $devname eq 'global'
|
|
||||||
&& $init_done
|
|
||||||
)
|
|
||||||
|| (
|
|
||||||
grep m{^INITIALIZED$}xms,
|
|
||||||
@{$events} or grep m{^REREADCFG$}xms,
|
|
||||||
@{$events} or grep m{^MODIFIED.$name$}xms,
|
|
||||||
@{$events}
|
|
||||||
)
|
|
||||||
&& $devname eq 'global'
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Set {
|
|
||||||
my $hash = shift // return;
|
|
||||||
my $aArg = shift // return;
|
|
||||||
|
|
||||||
my $name = shift @$aArg;
|
|
||||||
my $cmd = shift @$aArg
|
|
||||||
// return qq{"set $name" needs at least one argument};
|
|
||||||
|
|
||||||
if ( lc $cmd eq 'addpassword' ) {
|
|
||||||
return "please set Attribut bTS_User first"
|
|
||||||
if ( AttrVal( $name, 'bTS_User', 'none' ) eq 'none' );
|
|
||||||
return "usage: $cmd <password>" if ( scalar( @{$aArg} ) != 1 );
|
|
||||||
|
|
||||||
StorePassword( $hash, $name, $aArg->[0] );
|
|
||||||
}
|
|
||||||
elsif ( lc $cmd eq 'deletepassword' ) {
|
|
||||||
return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 );
|
|
||||||
|
|
||||||
DeletePassword($hash);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
my $list = (
|
|
||||||
defined( ReadPassword( $hash, $name ) )
|
|
||||||
? 'deletepassword:noArg'
|
|
||||||
: 'addpassword'
|
|
||||||
);
|
|
||||||
|
|
||||||
return 'Unknown argument ' . $cmd . ', choose one of ' . $list;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub Rename {
|
|
||||||
my $new = shift;
|
|
||||||
my $old = shift;
|
|
||||||
|
|
||||||
my $hash = $defs{$new};
|
|
||||||
|
|
||||||
StorePassword( $hash, $new, ReadPassword( $hash, $old ) );
|
|
||||||
setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef );
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub PushToStorage {
|
|
||||||
my $hash = shift;
|
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
|
|
||||||
Log3( $name, 4, "backupToStorage ($name) - push to storage function" );
|
|
||||||
|
|
||||||
require "SubProcess.pm";
|
|
||||||
my $subprocess = SubProcess->new( { onRun => \&FileUpload } );
|
|
||||||
|
|
||||||
my $backupFile = ReadingsVal( $name, 'fhemBackupFile', 'none' );
|
|
||||||
|
|
||||||
my @fileNameAtStorage_array = split( '/', $backupFile );
|
|
||||||
my $fileNameAtStorage = $fileNameAtStorage_array[$#fileNameAtStorage_array];
|
|
||||||
|
|
||||||
$subprocess->{curl} = qx(which curl);
|
|
||||||
chomp($subprocess->{curl});
|
|
||||||
$subprocess->{type} = $hash->{STORAGETYPE};
|
|
||||||
$subprocess->{host} = AttrVal( $name, 'bTS_Host', '' );
|
|
||||||
$subprocess->{user} = AttrVal( $name, 'bTS_User', '' );
|
|
||||||
$subprocess->{pass} = ReadPassword( $hash, $name );
|
|
||||||
$subprocess->{path} = AttrVal( $name, 'bTS_Path', '' );
|
|
||||||
$subprocess->{backupfile} = $backupFile;
|
|
||||||
$subprocess->{fileNameAtStorage} = $fileNameAtStorage;
|
|
||||||
|
|
||||||
my $pid = $subprocess->run();
|
|
||||||
|
|
||||||
readingsSingleUpdate( $hash, 'state', ' file upload in progress', 1 );
|
|
||||||
|
|
||||||
if ( !defined($pid) ) {
|
|
||||||
Log3( $name, 1,
|
|
||||||
"backupToStorage ($name) - Cannot execute command asynchronously" );
|
|
||||||
|
|
||||||
CleanSubprocess($hash);
|
|
||||||
readingsSingleUpdate( $hash, 'state',
|
|
||||||
'Cannot execute command asynchronously', 1 );
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log3( $name, 4,
|
|
||||||
"backupToStorage ($name) - execute command asynchronously (PID=$pid)"
|
|
||||||
);
|
|
||||||
|
|
||||||
$hash->{".fhem"}{subprocess} = $subprocess;
|
|
||||||
|
|
||||||
InternalTimer( gettimeofday() + 1,
|
|
||||||
"FHEM::backupToStorage::PollChild", $hash );
|
|
||||||
Log3( $hash, 4,
|
|
||||||
"backupToStorage ($name) - control passed back to main loop." );
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub PollChild {
|
|
||||||
my $hash = shift;
|
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
|
|
||||||
if ( defined( $hash->{".fhem"}{subprocess} ) ) {
|
|
||||||
my $subprocess = $hash->{".fhem"}{subprocess};
|
|
||||||
my $json = $subprocess->readFromChild();
|
|
||||||
|
|
||||||
if ( !defined($json) ) {
|
|
||||||
Log3( $name, 5,
|
|
||||||
"backupToStorage ($name) - still waiting ("
|
|
||||||
. $subprocess->{lasterror}
|
|
||||||
. ")." );
|
|
||||||
InternalTimer( gettimeofday() + 1,
|
|
||||||
"FHEM::backupToStorage::PollChild", $hash );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log3( $name, 4,
|
|
||||||
"backupToStorage ($name) - got result from asynchronous parsing."
|
|
||||||
);
|
|
||||||
$subprocess->wait();
|
|
||||||
Log3( $name, 4,
|
|
||||||
"backupToStorage ($name) - asynchronous finished." );
|
|
||||||
|
|
||||||
CleanSubprocess($hash);
|
|
||||||
WriteReadings( $hash, $json );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
######################################
|
|
||||||
# Begin Childprozess
|
|
||||||
######################################
|
|
||||||
sub FileUpload {
|
|
||||||
my $subprocess = shift;
|
|
||||||
my $response = {};
|
|
||||||
|
|
||||||
if ( $subprocess->{type} eq 'Nextcloud' ) {
|
|
||||||
my ($returnString,$returnCode) = ExecuteNCupload($subprocess);
|
|
||||||
|
|
||||||
$response->{ncUpload} = ( $returnCode == 72057594037927935
|
|
||||||
&& $returnString eq ''
|
|
||||||
? 'upload successfully'
|
|
||||||
: $returnString );
|
|
||||||
}
|
|
||||||
|
|
||||||
my $json = eval { encode_json($response) };
|
|
||||||
if ($@) {
|
|
||||||
Log3( 'backupToStorage File Upload',
|
|
||||||
1, "backupToStorage - JSON error: $@" );
|
|
||||||
$json = '{"jsonerror":"$@"}';
|
|
||||||
}
|
|
||||||
|
|
||||||
$subprocess->writeToParent($json);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub ExecuteNCupload {
|
|
||||||
my $subprocess = shift;
|
|
||||||
|
|
||||||
my $command = $subprocess->{curl};
|
|
||||||
$command .= ' -s -u ';
|
|
||||||
$command .= $subprocess->{user} . ':' . $subprocess->{pass};
|
|
||||||
$command .= ' -T ' . $subprocess->{backupfile};
|
|
||||||
$command .= ' "https://';
|
|
||||||
$command .= $subprocess->{host};
|
|
||||||
$command .= '/remote.php/dav/files/';
|
|
||||||
$command .= $subprocess->{user};
|
|
||||||
$command .= $subprocess->{path};
|
|
||||||
$command .= '/';
|
|
||||||
$command .= $subprocess->{fileNameAtStorage};
|
|
||||||
$command .= '"';
|
|
||||||
|
|
||||||
return ExecuteCommand($command);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub ExecuteCommand {
|
|
||||||
my $command = join q{ }, @_;
|
|
||||||
return ( $_ = qx{$command 2>&1}, $? >> 8 );
|
|
||||||
}
|
|
||||||
|
|
||||||
######################################
|
|
||||||
# End Childprozess
|
|
||||||
######################################
|
|
||||||
|
|
||||||
sub CleanSubprocess {
|
|
||||||
my $hash = shift;
|
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
|
|
||||||
delete( $hash->{".fhem"}{subprocess} );
|
|
||||||
Log3( $name, 4, "backupToStorage ($name) - clean Subprocess" );
|
|
||||||
}
|
|
||||||
|
|
||||||
sub StorePassword {
|
|
||||||
my $hash = shift;
|
|
||||||
my $name = shift;
|
|
||||||
my $password = shift;
|
|
||||||
|
|
||||||
my $index = $hash->{TYPE} . "_" . $name . "_passwd";
|
|
||||||
my $key = getUniqueId() . $index;
|
|
||||||
my $enc_pwd = "";
|
|
||||||
|
|
||||||
if ( eval "use Digest::MD5;1" ) {
|
|
||||||
|
|
||||||
$key = Digest::MD5::md5_hex( unpack "H*", $key );
|
|
||||||
$key .= Digest::MD5::md5_hex($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
for my $char ( split //, $password ) {
|
|
||||||
|
|
||||||
my $encode = chop($key);
|
|
||||||
$enc_pwd .= sprintf( "%.2x", ord($char) ^ ord($encode) );
|
|
||||||
$key = $encode . $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $err = setKeyValue( $index, $enc_pwd );
|
|
||||||
DoTrigger( $name, 'password add' );
|
|
||||||
|
|
||||||
return "error while saving the password - $err" if ( defined($err) );
|
|
||||||
|
|
||||||
return "password successfully saved";
|
|
||||||
}
|
|
||||||
|
|
||||||
sub ReadPassword {
|
|
||||||
my $hash = shift;
|
|
||||||
my $name = shift;
|
|
||||||
|
|
||||||
my $index = $hash->{TYPE} . "_" . $name . "_passwd";
|
|
||||||
my $key = getUniqueId() . $index;
|
|
||||||
my ( $password, $err );
|
|
||||||
|
|
||||||
Log3( $name, 4, "backupToStorage ($name) - Read password from file" );
|
|
||||||
|
|
||||||
( $err, $password ) = getKeyValue($index);
|
|
||||||
|
|
||||||
if ( defined($err) ) {
|
|
||||||
|
|
||||||
Log3( $name, 3,
|
|
||||||
"backupToStorage ($name) - unable to read password from file: $err"
|
|
||||||
);
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( defined($password) ) {
|
|
||||||
if ( eval "use Digest::MD5;1" ) {
|
|
||||||
$key = Digest::MD5::md5_hex( unpack "H*", $key );
|
|
||||||
$key .= Digest::MD5::md5_hex($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
my $dec_pwd = '';
|
|
||||||
|
|
||||||
for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/g ) ) {
|
|
||||||
|
|
||||||
my $decode = chop($key);
|
|
||||||
$dec_pwd .= chr( ord($char) ^ ord($decode) );
|
|
||||||
$key = $decode . $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $dec_pwd;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Log3( $name, 3, "backupToStorage ($name) - No password in file" );
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub DeletePassword {
|
|
||||||
my $hash = shift;
|
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
|
|
||||||
setKeyValue( $hash->{TYPE} . "_" . $name . "_passwd", undef );
|
|
||||||
DoTrigger( $name, 'password remove' );
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub CheckAttributsForCredentials {
|
|
||||||
my $hash = shift;
|
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
|
|
||||||
my $ncUser = AttrVal( $name, 'bTS_User', 'none' );
|
|
||||||
my $ncPass = ReadPassword( $hash, $name );
|
|
||||||
my $ncHost = AttrVal( $name, 'bTS_Host', 'none' );
|
|
||||||
my $status = 'ready';
|
|
||||||
|
|
||||||
$status = ( $status eq 'ready'
|
|
||||||
&& $ncUser eq 'none'
|
|
||||||
? 'no user credential attribut'
|
|
||||||
: $status eq 'ready'
|
|
||||||
&& $ncHost eq 'none'
|
|
||||||
? 'no host credential attribut'
|
|
||||||
: $status eq 'ready'
|
|
||||||
&& !defined($ncPass)
|
|
||||||
? 'no password set'
|
|
||||||
: $status
|
|
||||||
);
|
|
||||||
|
|
||||||
return readingsSingleUpdate( $hash, 'state', $status, 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
sub WriteReadings {
|
|
||||||
my $hash = shift;
|
|
||||||
my $json = shift;
|
|
||||||
|
|
||||||
my $name = $hash->{NAME};
|
|
||||||
|
|
||||||
my $decode_json = eval { decode_json($json) };
|
|
||||||
if ($@) {
|
|
||||||
Log3( $name, 2, "backupToStorage ($name) - JSON error: $@" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
readingsBeginUpdate($hash);
|
|
||||||
readingsBulkUpdate( $hash, 'state', 'ready' );
|
|
||||||
readingsBulkUpdate( $hash, 'uploadState', $decode_json->{ncUpload} );
|
|
||||||
readingsEndUpdate( $hash, 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
1;
|
|
Loading…
x
Reference in New Issue
Block a user