From 25898f2e8d434809e53a46fc95857a4f69765e29 Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Tue, 25 May 2021 16:51:35 +0000 Subject: [PATCH] 50_SSFile: Module that integrates Synology Sile Station git-svn-id: https://svn.fhem.de/fhem/trunk@24509 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/50_SSFile.pm | 3238 ++++++++++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 1 + 3 files changed, 3240 insertions(+) create mode 100644 fhem/FHEM/50_SSFile.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index 025766d17..5939c4ee2 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 50_SSFile: Module that integrates Synology Sile Station - bugfix: 98_WeekdayTimer: CONDITION now again is checked for entire day - feature: 74_GardenaSmartDevice: add support for SmartIrrigationControl add custom schedule breake ic24 diff --git a/fhem/FHEM/50_SSFile.pm b/fhem/FHEM/50_SSFile.pm new file mode 100644 index 000000000..00c9e5fe5 --- /dev/null +++ b/fhem/FHEM/50_SSFile.pm @@ -0,0 +1,3238 @@ +######################################################################################################################## +# $Id$ +######################################################################################################################### +# 50_SSFile.pm +# +# (c) 2020-2021 by Heiko Maaz +# e-mail: Heiko dot Maaz at t-online dot de +# +# This Module integrate the Synology File Station into FHEM +# +# This script is part of fhem. +# +# Fhem 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 +# (at your option) any later version. +# +# Fhem 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. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +######################################################################################################################### +# +# Definition: define SSFile [ServerPort] [Protocol] +# +# Example: define SynFile SSFile 192.168.2.20 [5000] [HTTP(S)] +# +package FHEM::SSFile; ## no critic 'package' + +use strict; +use warnings; +use utf8; +eval "use JSON;1;" or my $SSFileMM = "JSON"; ## no critic 'eval' # Debian: apt-get install libjson-perl +use Data::Dumper; # Perl Core module +use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt +use FHEM::SynoModules::API qw(apistatic); # API Modul + +use FHEM::SynoModules::SMUtils qw( completeAPI + showAPIinfo + showModuleInfo + addSendqueue + listSendqueue + purgeSendqueue + checkSendRetry + startFunctionDelayed + evaljson + smUrlEncode + getClHash + delClHash + delReadings + setCredentials + getCredentials + showStoredCredentials + setReadingErrorState + setReadingErrorNone + login + logout + moduleVersion + trim + slurpFile + jboolmap + ); # Hilfsroutinen Modul + +use FHEM::SynoModules::ErrCodes qw(expErrors); # Error Code Modul +use MIME::Base64; +use POSIX qw(strftime); +use Time::HiRes qw(gettimeofday); +use HttpUtils; +use Encode; +use Encode::Guess; +use File::Find; +use File::Glob ':bsd_glob'; +no if $] >= 5.017011, warnings => 'experimental::smartmatch'; +eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval' + +# no if $] >= 5.017011, warnings => 'experimental'; + +# Run before module compilation +BEGIN { + # Import from main:: + GP_Import( + qw( + attr + AttrVal + BlockingCall + BlockingKill + BlockingInformParent + CancelDelayedShutdown + CommandSet + CommandAttr + CommandDelete + CommandDefine + CommandGet + CommandSetReading + CommandTrigger + Debug + data + defs + devspec2array + FileWrite + FileDelete + FileRead + FmtTime + FmtDateTime + fhemTimeLocal + HttpUtils_NonblockingGet + init_done + InternalTimer + IsDisabled + Log3 + modules + parseParams + readingFnAttributes + ReadingsVal + RemoveInternalTimer + ResolveDateWildcards + readingsBeginUpdate + readingsBulkUpdate + readingsBulkUpdateIfChanged + readingsEndUpdate + readingsSingleUpdate + setKeyValue + urlEncode + urlDecode + ) + ); + + # Export to main context with different name + # my $pkg = caller(0); + # my $main = $pkg; + # $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/gx; + # foreach (@_) { + # *{ $main . $_ } = *{ $pkg . '::' . $_ }; + # } + GP_Export( + qw( + Initialize + ) + ); +} + +# Versions History intern +my %vNotesIntern = ( + "1.0.0" => "25.05.2021 ready for check in ", + "0.8.1" => "24.05.2021 fix FHEM crash when malfomed JSON is received ". + "Forum: https://forum.fhem.de/index.php/topic,115371.msg1158531.html#msg1158531 ", + "0.8.0" => "18.03.2021 extend commandref, switch to 'stable' ", + "0.7.7" => "07.01.2021 avoid FHEM crash if Cache file content is not valid JSON format ", + "0.7.6" => "20.12.2020 minor change to avoid increase memory ", + "0.7.5" => "07.12.2020 minor fix avoid overtakers ", + "0.7.4" => "30.11.2020 add mtime, crtime to uploaded files ", + "0.7.3" => "29.11.2020 fix (prepare)Download without dest= option", + "0.7.2" => "22.11.2020 undef variables containing a lot of data in execOp ", + "0.7.1" => "08.11.2020 fix download, fix perl warning while upload not existing files ", + "0.7.0" => "02.11.2020 new set command deleteRemoteObj, fix download object with space in name ", + "0.6.0" => "30.10.2020 Upload files may contain wildcards *. ", + "0.5.0" => "26.10.2020 new Setter Upload and fillup upload queue asynchronously, some more improvements around Upload ", + "0.4.0" => "18.10.2020 add reqtype to addSendqueue, new Setter prepareDownload ", + "0.3.0" => "16.10.2020 create Reading Hash instead of Array ", + "0.2.0" => "16.10.2020 some changes in subroutines ", + "0.1.0" => "12.10.2020 initial " +); + +my %hset = ( # Hash für Set-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials) + credentials => { fn => \&_setcredentials, needcred => 0 }, + eraseReadings => { fn => \&_seteraseReadings, needcred => 0 }, + listQueue => { fn => \&listSendqueue, needcred => 0 }, + logout => { fn => \&_setlogout, needcred => 0 }, + purgeQueue => { fn => \&purgeSendqueue, needcred => 0 }, + startQueue => { fn => \&_setstartQueue, needcred => 1 }, + Download => { fn => \&_setDownload, needcred => 1 }, + prepareDownload => { fn => \&_setDownload, needcred => 1 }, + Upload => { fn => \&_setUpload, needcred => 1 }, + prepareUpload => { fn => \&_setUpload, needcred => 1 }, + listUploadsDone => { fn => \&_setlistUploadsDone, needcred => 0 }, + deleteUploadsDone => { fn => \&_setdeleteUploadsDone, needcred => 0 }, + deleteRemoteObject => { fn => \&_setdeleteRemoteObject, needcred => 1 }, +); + +my %hget = ( # Hash für Get-Funktion (needcred => 1: Funktion benötigt gesetzte Credentials) + apiInfo => { fn => \&_getapiInfo, needcred => 1 }, + backgroundTaskList => { fn => \&_getbackgroundTaskList, needcred => 1 }, + fileStationInfo => { fn => \&_getfilestationInfo, needcred => 1 }, + remoteFileInfo => { fn => \&_getremoteFileInfo, needcred => 1 }, + remoteFolderList => { fn => \&_getRemoteFolderList, needcred => 1 }, + storedCredentials => { fn => \&_getstoredCredentials, needcred => 1 }, + versionNotes => { fn => \&_getversionNotes, needcred => 0 }, +); + +my %hmodep = ( # Hash für Opmode Parser. + fileStationInfo => { fn => \&_parsefilestationInfo, doevt => 1 }, # doevt: 1 - Events dürfen ausgelöste werden. 0 - keine Events + backgroundTask => { fn => \&_parsebackgroundTaskList, doevt => 1 }, + shareList => { fn => \&_parseFiFo, doevt => 1 }, + remoteFolderList => { fn => \&_parseFiFo, doevt => 0 }, + remoteFileInfo => { fn => \&_parseFiFo, doevt => 1 }, + download => { fn => \&_parseDownload, doevt => 1 }, + upload => { fn => \&_parseUpload, doevt => 1 }, + deleteRemoteObj => { fn => \&_parsedeleteRemoteObject, doevt => 1 }, +); + +# Versions History extern +my %vNotesExtern = ( + "0.6.0" => "30.10.2020 A new Set command Upload is integrated and the fillup upload queue routine is running asynchronously.
". + "Some more improvements around Upload were done, e.g. Upload files may contain wildcards *.", + "0.1.0" => "12.10.2020 initial " +); + +# Hints EN +my %vHintsExt_en = ( + "2" => "When defining the upload target paths POSIX %-Wildcards can be used as part of the target path. ". + "This way changing upload targets can be created depending on the current timestamp.
". + "Examples of prominent wildcards:

". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "
Specification replaced by Example
%%a The abbreviated weekday name according to the current locale Thu
%y Year, last two digits (00-99) 01
%Y Year incl. century 2020
%m Month as decimal number (01-12) 08
%%d The day of the month as decimal number (01-31) 23
%H The hour in 24-hour format (00-23) 14
%M The minute as decimal number (00-59) 55
%S The second as decimal number (00-60) 02
%V The ISO 8601 week number of the current year as decimal number (01-53) 34
%T The time in 24-hour notation (%H:%M:%S) 14:55:02
". + "
" + , + "1" => "The module integrates Synology File Station with FHEM. " +); + +# Hints DE +my %vHintsExt_de = ( + "2" => encode ("utf8", "Bei der Definition der Upload Zielpfade könnnen POSIX %-Wildcards als Bestandteil des Zielpfads verwendet werden. ". + "Damit können wechselnde Uploadziele in Abhängigkeit des aktuellen Timestamps erzeugt werden.
". + "Beispiele prominenter Wildcards:

". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "". + "
Spezifizierung ersetzt durch Beispiel
%%a Der abgekürzte Wochentagsname entsprechend dem aktuellen Gebietsschema Mo
%y Jahr, letzte zwei Ziffern (00-99) 01
%Y Jahr incl. Jahrhundert 2020
%m Monat als Dezimalzahl (01-12) 08
%%d Der Tag des Monats als Dezimalzahl (01-31) 23
%H Die Stunde im 24-Stunden-Format (00-23) 14
%M Die Minute als Dezimalzahl (00-59) 55
%S Die Sekunde als Dezimalzahl (00-60) 02
%V Die ISO 8601-Wochennummer des laufenden Jahres als Dezimalzahl (01-53) 34
%T Die Uhrzeit in 24-Stunden-Notation (%H:%M:%S) 14:55:02
". + "
" + ), + "1" => "Das Modul integriert die Synology File Station in FHEM. " +); + +# Standardvariablen +my $splitstr = "!_ESC_!"; # Split-String zur Übergabe in getCredentials, login & Co. +my $queueStartFn = "FHEM::SSFile::getApiSites"; # Startfunktion zur Queue-Abarbeitung +my $mbf = 1048576; # Divisionsfaktor für Megabytes +my $kbf = 1024; # Divisionsfaktor für Kilobytes +my $bound = "wNWT9spu8GvTg4TJo1iN"; # Boundary for Multipart POST +my $excluplddef = ".*@.*"; # vom Upload per default excludierte Objekte (Regex) +my $uldcache = $attr{global}{modpath}."/FHEM/FhemUtils/Uploads_SSFile_"; # Filename-Fragment für hochgeladene Files (wird mit Devicename ergänzt) + +################################################################ +sub Initialize { + my ($hash) = @_; + $hash->{DefFn} = \&Define; + $hash->{UndefFn} = \&Undef; + $hash->{DeleteFn} = \&Delete; + $hash->{SetFn} = \&Set; + $hash->{GetFn} = \&Get; + $hash->{AttrFn} = \&Attr; + $hash->{DelayedShutdownFn} = \&DelayedShutdown; + + # Darstellung FHEMWEB + # $hash->{FW_summaryFn} = \&FWsummaryFn; + # $hash->{FW_addDetailToSummary} = 1 ; # zusaetzlich zu der Device-Summary auch eine Neue mit dem Inhalt von DetailFn angezeigt + # $hash->{FW_detailFn} = \&FWdetailFn; + $hash->{FW_deviceOverview} = 1; + + $hash->{AttrList} = "additionalInfo:multiple-strict,real_path,size,owner,time,perm,mount_point_type,type,volume_status ". + "disable:1,0 ". + "excludeFromUpload:textField-long ". + "interval ". + "loginRetries:1,2,3,4,5,6,7,8,9,10 ". + "noAsyncFillQueue:1,0 ". + "showPassInLog:1,0 ". + "timeout ". + $readingFnAttributes; + + FHEM::Meta::InitMod( __FILE__, $hash ) if(!$modMetaAbsent); # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html) + +return; +} + +################################################################ +# define SSFile 192.168.2.10 [5000] [HTTP(S)] +# [1] [2] [3] [4] +# +################################################################ +sub Define { + my ($hash, $def) = @_; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + return "Error: Perl module ".$SSFileMM." is missing. Install it on Debian with: sudo apt-get install libjson-perl" if($SSFileMM); + + my @a = split("[ \t][ \t]*", $def); + + if(int(@a) < 2) { + return "You need to specify more parameters.\n". "Format: define SSFile [Port] [HTTP(S)] [Tasks]"; + } + + shift @a; shift @a; + + my $addr = ($a[0] && $a[0] ne "Tasks") ? $a[0] : ""; + my $port = ($a[1] && $a[1] ne "Tasks") ? $a[1] : 5000; + my $prot = ($a[2] && $a[2] ne "Tasks") ? lc($a[2]) : "http"; + + my $model = "unspecified"; + + $hash->{SERVERADDR} = $addr; + $hash->{SERVERPORT} = $port; + $hash->{MODEL} = "Calendar"; + $hash->{PROTOCOL} = $prot; + $hash->{MODEL} = $model; + $hash->{RESEND} = "next planned SendQueue start: immediately by next entry"; + $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden + + CommandAttr(undef, "$name room SSFile"); + + my $params = { + hash => $hash, + notes => \%vNotesIntern, + useAPI => 1, + useSMUtils => 1, + useErrCodes => 1 + }; + use version 0.77; our $VERSION = moduleVersion ($params); # Versionsinformationen setzen + + getCredentials($hash,1,"credentials",$splitstr); # Credentials lesen + + $data{$type}{$name}{sendqueue}{index} = 0; # Index der Sendequeue initialisieren + + initOnBoot($name); # initiale Routinen nach Start ausführen , verzögerter zufälliger Start + +return; +} + +################################################################ +# Die Undef-Funktion wird aufgerufen wenn ein Gerät mit delete +# gelöscht wird oder bei der Abarbeitung des Befehls rereadcfg, +# der ebenfalls alle Geräte löscht und danach das +# Konfigurationsfile neu einliest. +# Funktion: typische Aufräumarbeiten wie das +# saubere Schließen von Verbindungen oder das Entfernen von +# internen Timern, sofern diese im Modul zum Pollen verwendet +# wurden. +################################################################ +sub Undef { + my $hash = shift; + my $arg = shift; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + BlockingKill($hash->{HELPER}{RUNNING_PID}) if($hash->{HELPER}{RUNNING_PID}); + delete $data{$type}{$name}; + + RemoveInternalTimer($name); + +return; +} + +####################################################################################################### +# Mit der X_DelayedShutdown Funktion kann eine Definition das Stoppen von FHEM verzögern um asynchron +# hinter sich aufzuräumen. +# Je nach Rückgabewert $delay_needed wird der Stopp von FHEM verzögert (0|1). +# Sobald alle nötigen Maßnahmen erledigt sind, muss der Abschluss mit CancelDelayedShutdown($name) an +# FHEM zurückgemeldet werden. +####################################################################################################### +sub DelayedShutdown { + my $hash = shift; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + if($data{$type}{$name}{uploaded}) { # Cache File für Uploads schreiben + my @upl; + my $json = encode_json ($data{$type}{$name}{uploaded}); + push @upl, $json; + my $file = $uldcache.$name; + my $error = FileWrite($file, @upl); + if ($error) { + Log3 ($name, 2, qq{$name - ERROR writing cache file "$file": $error}); + } + } + + if($hash->{HELPER}{SID}) { + logout($hash, $data{$type}{$name}{fileapi}, $splitstr); # Session alter User beenden falls vorhanden + return 1; + } + +return 0; +} + +################################################################# +# Wenn ein Gerät in FHEM gelöscht wird, wird zuerst die Funktion +# X_Undef aufgerufen um offene Verbindungen zu schließen, +# anschließend wird die Funktion X_Delete aufgerufen. +# Funktion: Aufräumen von dauerhaften Daten, welche durch das +# Modul evtl. für dieses Gerät spezifisch erstellt worden sind. +# Es geht hier also eher darum, alle Spuren sowohl im laufenden +# FHEM-Prozess, als auch dauerhafte Daten bspw. im physikalischen +# Gerät zu löschen die mit dieser Gerätedefinition zu tun haben. +################################################################# +sub Delete { + my $hash = shift; + my $arg = shift; + my $name = $hash->{NAME}; + my $index = $hash->{TYPE}."_".$hash->{NAME}."_credentials"; + + setKeyValue($index, undef); # gespeicherte Credentials löschen + my $file = $uldcache.$name; + my $error = FileDelete($file); # Cache File für Uploads löschen + if ($error) { + Log3 ($name, 2, qq{$name - ERROR deleting cache file "$file": $error}); + } + +return; +} + +################################################################ +sub Attr { + my $cmd = shift; + my $name = shift; + my $aName = shift; + my $aVal = shift; + my $hash = $defs{$name}; + my $model = $hash->{MODEL}; + + my ($do,$val); + + # $cmd can be "del" or "set" + # $name is device name + # aName and aVal are Attribute name and value + + if ($aName eq "disable") { + if($cmd eq "set") { + $do = $aVal ? 1 : 0; + } + $do = 0 if($cmd eq "del"); + $val = ($do ? "disabled" : "initialized"); + + if ($do) { + delReadings ($name, 0); + RemoveInternalTimer($hash); + } + else { + InternalTimer(gettimeofday()+2, "FHEM::SSFile::initOnBoot", $hash, 0) if($init_done); + } + + readingsBeginUpdate($hash); + readingsBulkUpdate ($hash, "state", $val); + readingsEndUpdate ($hash, 1); + } + + if ($cmd eq "set") { + if ($aName =~ m/interval/x && $aVal !~ /^[0-9]+$/x) { + return qq{The value of $aName is not valid. Use only integers 0-9 !}; + } + if($aName =~ m/interval/x) { + RemoveInternalTimer($name,"FHEM::SSFile::periodicCall"); + InternalTimer (gettimeofday()+1.0, "FHEM::SSFile::periodicCall", $name, 0); + } + } + +return; +} + +############################################################################################# +# Setter +############################################################################################# +sub Set { + my ($hash, @a) = @_; + return "\"set X\" needs at least an argument" if ( @a < 2 ); + my $name = shift @a; + my $opt = shift @a; + my $arg = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @a; ## no critic 'Map blocks' + my $prop = shift @a; + + my $model = $hash->{MODEL}; + my $type = $hash->{TYPE}; + + my ($success,$setlist); + + return if(IsDisabled($name)); + + my $idxlist = join(",", sort{$a<=>$b} keys %{$data{$type}{$name}{sendqueue}{entries}}); + + $setlist = "Unknown argument $opt, choose one of "; + + if(!$hash->{CREDENTIALS}) { # initiale setlist für neue Devices + $setlist .= "credentials "; + } + + if($hash->{CREDENTIALS}) { + $setlist .= "credentials ". + "deleteUploadsDone:noArg ". + "deleteRemoteObject:textField-long ". + "Download:textField-long ". + "Upload:textField-long ". + "eraseReadings:noArg ". + "listQueue:noArg ". + "listUploadsDone:noArg ". + "logout:noArg ". + "prepareDownload:textField-long ". + "prepareUpload:textField-long ". + "startQueue:noArg ". + ($idxlist ? "purgeQueue:-all-,-permError-,$idxlist " : "purgeQueue:-all-,-permError- ") + ; + } + + my $params = { + hash => $hash, + name => $name, + opt => $opt, + arg => $arg, + prop => $prop + }; + + if($hset{$opt} && defined &{$hset{$opt}{fn}}) { + my $ret = q{}; + + if (!$hash->{CREDENTIALS} && $hset{$opt}{needcred}) { + return qq{Credentials of $name are not set. Make sure they are set with "set $name credentials "}; + } + + $ret = &{$hset{$opt}{fn}} ($params); + + return $ret; + } + +return "$setlist"; +} + +###################################################################################### +# Setter credentials +###################################################################################### +sub _setcredentials { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $arg = $paref->{arg}; + + return qq{The command "$opt" needs an argument.} if (!$arg); + + my ($a,$h) = parseParams($arg); + my $user = $a->[0]; + my $pw = $a->[1] // q{}; + + if($hash->{HELPER}{SID}) { + my $type = $hash->{TYPE}; + logout($hash, $data{$type}{$name}{fileapi}, $splitstr); # Session alter User beenden falls vorhanden + } + + my ($success) = setCredentials($hash, "credentials", $user, $pw, $splitstr); + + if($success) { + return "credentials saved successfully"; + } + else { + return "Error while saving credentials - see logfile for details"; + } + +return; +} + +###################################################################################### +# Setter Download, prepareDownload +###################################################################################### +sub _setDownload { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $arg = $paref->{arg}; + + if(!$arg) { + return qq{The command "$opt" needs an argument !} + } + + # Schema: name => Name, + # opmode => operation mode, + # api => API (siehe $data{$type}{$name}{fileapi}), + # method => auszuführende API-Methode, + # params => "spezifische API-Parameter> + + my ($s,$d) = split "dest=", $arg; + $d = smUrlEncode ($d); + + $arg = $s." dest=".$d if($d); + my ($a,$h) = parseParams ($arg); + my $fp = $a->[0]; + + if(!$fp) { + return qq{No source file or directory specified for download !} + } + + $fp = smUrlEncode ($fp); + + delReadings ($name, 0); + + my @dld = split ",", $fp; + + for my $dl (@dld) { + $dl =~ s/"//xg; + my $file = (split "\/", $dl)[-1]; + my $dest = $h->{dest}; + $dl = qq{"$dl"}; + + if(!$dest) { + $dest = $attr{global}{modpath}."/".$file; + } + + if($dest =~ /\/$/x) { + $dest .= $file; + } + + my $params = { + name => $name, + opmode => "download", + api => "DOWNLOAD", + method => "download", + params => "&path=$dl", + reqtype => "GET", + header => "Accept: application/json", + dest => $dest + }; + + addSendqueue ($params); + } + + if($opt ne "prepareDownload") { + getApiSites ($name); # Queue starten + } + else { + readingsBeginUpdate ($hash); + readingsBulkUpdateIfChanged ($hash, "Errorcode", "none" ); + readingsBulkUpdateIfChanged ($hash, "Error", "none" ); + readingsBulkUpdate ($hash, "QueueAddDownload", $a->[0]); + readingsBulkUpdate ($hash, "state", "done" ); + readingsEndUpdate ($hash, 1); + } + +return; +} + +###################################################################################### +# Setter Upload, prepareUpload +###################################################################################### +sub _setUpload { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $arg = $paref->{arg}; + + if(!$arg) { + return qq{The command "$opt" needs an argument !} + } + + # Schema: name => Name, + # opmode => operation mode, + # api => API (siehe $data{$type}{$name}{fileapi}), + # method => auszuführende API-Methode, + # params => "spezifische API-Parameter> + + my ($a,$h) = parseParams ($arg); + my $fp = $a->[0]; + + if(!$fp) { + return qq{No source file or directory specified for upload !} + } + + my $remDir = $h->{dest}; + if(!$remDir) { + return qq{The command "$opt" needs a destination for upload like "dest=/home/upload" !} + } + + $remDir =~ s/\/$//x; + my @t = localtime; + $remDir = ResolveDateWildcards ($remDir, @t); # POSIX Wildcards für Verzeichnis auflösen + + my $ow = $h->{ow} // "true"; # Überschreiben Steuerbit + my $cdir = $h->{cdir} // "true"; # create Directory Steuerbit + my $mode = $h->{mode} // "full"; # Uploadverfahren (full, inc, new:days) + my $struc = $h->{struc} // "true"; # true: Übertragung Struktur erhaltend, false: alles landet im angegebenen Dest-Verzeichnis ohne Berücksichtigung des Quellverezchnisses + + my @uld = split ",", $fp; + + my @all; + for my $obj (@uld) { # nicht existierende objekte aussondern + my @globes = bsd_glob ("$obj"); # Wildcards auflösen (https://perldoc.perl.org/functions/glob) + push (@all, @globes); + } + + my @afiles; + for my $file (@all) { + if (-e $file) { + push @afiles, $file; + next; + } + Log3 ($name, 3, qq{$name - The object "$file" doesn't exist or can't be dissolved, ignore it for upload}); + next; + } + + readingsSingleUpdate($hash, "state", "Wait for filling upload queue", 1); + + $paref->{allref} = \@afiles; + $paref->{remDir} = $remDir; + $paref->{ow} = $ow; + $paref->{cdir} = $cdir; + $paref->{struc} = $struc; + $paref->{mode} = $mode; + + my $found = exploreFiles ($paref); + $paref->{found} = $found; + + delReadings ($name, 0); + + delete $paref->{allref}; + + if(AttrVal($name, "noAsyncFillQueue", 0)) { # kein BlockingCall verwenden + __fillUploadQueue ($paref); + } + else { + my $timeout = 1800; + $hash->{HELPER}{RUNNING_PID} = BlockingCall("FHEM::SSFile::__fillUploadQueue", $paref, "FHEM::SSFile::__fillUploadQueueFinish", $timeout, "FHEM::SSFile::blockingTimeout", $hash); + $hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057 + } + +return; +} + +###################################################################################### +# extrahierte Filenamen für Upload in Queue eintragen +###################################################################################### +sub __fillUploadQueue { + my $paref = shift; + my $name = $paref->{name}; + my $remDir = $paref->{remDir}; + my $opt = $paref->{opt}; + my $ow = $paref->{ow}; + my $cdir = $paref->{cdir}; + my $struc = $paref->{struc}; + my $found = $paref->{found}; + + my $hash = $defs{$name}; + + # Log3 ($name, 3, "$name - all explored files for upload:\n".Dumper $found); + + for my $sn (keys %{$found}) { + my $fname = (split "\/", $found->{$sn}{lfile})[-1]; + + my $enc = guess_encoding($fname, qw/utf8/); # check ob Name UTF8 kodiert, Forum: https://forum.fhem.de/index.php/topic,115371.msg1158531.html#msg1158531 + if(!ref $enc ) { + $fname = encode ("utf8", $fname); + } + + my $mtime = $found->{$sn}{mtime} * 1000; # Angabe in Millisekunden + my $crtime = $found->{$sn}{crtime} * 1000; # Angabe in Millisekunden + my $dir = $remDir.$found->{$sn}{ldir}; # zusammengesetztes Zielverzeichnis (Struktur erhaltend - default) + + if($struc eq "false") { # Ziel nicht Struktur erhaltend (alle Files landen im Zielverzeichnis ohne Unterverzeichnisse) + $dir = $remDir; + } + + my $dat; + $dat .= addBodyPart (qq{content-disposition: form-data; name="path"}, $dir, "first"); + $dat .= addBodyPart (qq{content-disposition: form-data; name="create_parents"}, $cdir ); + $dat .= addBodyPart (qq{content-disposition: form-data; name="overwrite"}, $ow ); + $dat .= addBodyPart (qq{content-disposition: form-data; name="mtime"}, $mtime ); + $dat .= addBodyPart (qq{content-disposition: form-data; name="crtime"}, $crtime ); + $dat .= addBodyPart (qq{content-disposition: form-data; name="file"; filename="$fname"\r\nContent-Type: application/octet-stream}, "", "last" ); + + my $params = { + name => $name, + opmode => "upload", + api => "UPLOAD", + method => "upload", + reqtype => "POST", + header => "Content-Type: multipart/form-data, boundary=$bound", + lclFile => $found->{$sn}{lfile}, + postdata => $dat, + remFile => $dir."/".$fname + }; + + if(AttrVal($name, "noAsyncFillQueue", 0)) { + addSendqueue ($params); + } + else { + my $json = encode_json ($params); + BlockingInformParent("FHEM::SSFile::addQueueFromBlocking", [$json], 1); + } + } + + if(AttrVal($name, "noAsyncFillQueue", 0)) { + return __fillUploadQueueFinish ("$name|$opt"); + } + +return ("$name|$opt"); +} + +#################################################################################################### +# Finishing Upload Queue +#################################################################################################### +sub __fillUploadQueueFinish { + my $string = shift; + + my ($name, $opt) = split "\\|", $string; + my $hash = $defs{$name}; + + readingsSingleUpdate($hash, "state", "Upload queue fill finished", 1); + + my $ql = ReadingsVal($name, "QueueLength", 0); # zusätzlichen Event wenn Queue Aufbau fertig + CommandTrigger(undef, "$name QueueLength: $ql"); + + if($opt ne "prepareUpload") { + getApiSites ($name); # Queue starten + } + else { + readingsBeginUpdate ($hash); + readingsBulkUpdateIfChanged ($hash, "Errorcode", "none"); + readingsBulkUpdateIfChanged ($hash, "Error", "none"); + readingsBulkUpdate ($hash, "state", "done"); + readingsEndUpdate ($hash, 1); + } + +return; +} + +################################################################### +# SendQueue aus BlockingCall heraus füllen +################################################################### +sub addQueueFromBlocking { + my $json = shift; + + my $params = decode_json ($json); + + addSendqueue ($params); + +return 1; +} + +###################################################################################### +# Setter startQueue +###################################################################################### +sub _setstartQueue { + my $paref = shift; + my $name = $paref->{name}; + + my $ret = getApiSites($name); + + if($ret) { + return $ret; + } + else { + return "The SendQueue has been restarted."; + } + +return; +} + +###################################################################################### +# Setter eraseReadings +###################################################################################### +sub _seteraseReadings { + my $paref = shift; + my $name = $paref->{name}; + + delReadings($name, 0); # Readings löschen + +return; +} + +###################################################################################### +# Setter logout +###################################################################################### +sub _setlogout { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + + my $type = $hash->{TYPE}; + + logout($hash, $data{$type}{$name}{fileapi}, $splitstr); + +return; +} + +###################################################################################### +# Setter listUploadsDone +###################################################################################### +sub _setlistUploadsDone { + my $paref = shift; + my $hash = $paref->{hash}; + + my $ret = listUploadsDone ($hash); + +return $ret; +} + +###################################################################################### +# Setter deleteUploadsDone +###################################################################################### +sub _setdeleteUploadsDone { + my $paref = shift; + my $name = $paref->{name}; + my $hash = $paref->{hash}; + my $type = $hash->{TYPE}; + + delete $data{$type}{$name}{uploaded}; + +return; +} + +###################################################################################### +# Setter _setdeleteRemoteObject +###################################################################################### +sub _setdeleteRemoteObject { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $arg = $paref->{arg}; + + if(!$arg) { + return qq{The command "$opt" needs an argument !} + } + + delReadings ($name, 0); + + $arg = smUrlEncode ($arg); + my ($a,$h) = parseParams ($arg); + my $fp = $a->[0]; + + if(!$fp) { + return qq{No source file or directory specified for upload !} + } + + my $recursive = $h->{recursive} // "true"; # true: Dateien rekursiv löschen innerhalb eines Ordners, false: Nur Datei/Ordner der ersten Ebene löschen + + my @del = split ",", $fp; + + for my $dl (@del) { + $dl =~ s/"//xg; + $dl = qq{"$dl"}; + + my $params = { + name => $name, + opmode => "deleteRemoteObj", + api => "DELETE", + method => "delete", + params => "&recursive=$recursive&path=$dl", + reqtype => "GET", + header => "Accept: application/json", + timeout => 600, + }; + + addSendqueue ($params); + } + + getApiSites ($name); # Queue starten + +return; +} + +###################################################################################### +# Getter +###################################################################################### +sub Get { + my ($hash, @a) = @_; + return "\"get X\" needs at least an argument" if ( @a < 2 ); + my $name = shift @a; + my $opt = shift @a; + my $arg = join " ", map { my $p = $_; $p =~ s/\s//xg; $p; } @a; ## no critic 'Map blocks' + + my $getlist; + + if(!$hash->{CREDENTIALS}) { + return; + } + else { + $getlist = "Unknown argument $opt, choose one of ". + "apiInfo:noArg ". + "backgroundTaskList:noArg ". + "fileStationInfo:noArg ". + "remoteFolderList ". + "remoteFileInfo:textField-long ". + "storedCredentials:noArg ". + "versionNotes " + ; + } + + return if(IsDisabled($name)); + + my $params = { + hash => $hash, + name => $name, + opt => $opt, + arg => $arg + }; + + if($hget{$opt} && defined &{$hget{$opt}{fn}}) { + my $ret = q{}; + + if (!$hash->{CREDENTIALS} && $hget{$opt}{needcred}) { + return qq{Credentials of $name are not set. Make sure they are set with "set $name credentials "}; + } + + $ret = &{$hget{$opt}{fn}} ($params); + + return $ret; + } + +return $getlist; +} + +###################################################################################### +# Getter storedCredentials +###################################################################################### +sub _getstoredCredentials { + my $paref = shift; + my $hash = $paref->{hash}; + + my $out = showStoredCredentials ($hash, 1, $splitstr); + +return $out; +} + +###################################################################################### +# Getter apiInfo +# Informationen der verwendeten API abrufen und anzeigen +###################################################################################### +sub _getapiInfo { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + + delClHash ($name); + getClHash ($hash,1); # übergebenen CL-Hash (FHEMWEB) in Helper eintragen + + # Schema: name => Name, + # opmode => operation mode, + # api => API (siehe $data{$type}{$name}{fileapi}), + # method => auszuführende API-Methode, + # params => "spezifische API-Parameter + + my $params = { + name => $name, + opmode => "apiInfo", + api => "", + method => "", + params => "", + reqtype => "GET", + header => "Accept: application/json" + }; + + addSendqueue ($params); + getApiSites ($name); + +return; +} + +###################################################################################### +# Getter fileStationInfo +# Informationen der File Station abrufen +###################################################################################### +sub _getfilestationInfo { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + + # Schema: name => Name, + # opmode => operation mode, + # api => API (siehe $data{$type}{$name}{fileapi}), + # method => auszuführende API-Methode, + # params => "spezifische API-Parameter> + + my $params = { + name => $name, + opmode => "fileStationInfo", + api => "FSINFO", + method => "get", # Methode get statt getinfo -> Fehler in Docu ! + params => "", + reqtype => "GET", + header => "Accept: application/json" + }; + + addSendqueue ($params); + getApiSites ($name); + +return; +} + +###################################################################################### +# Getter backgroundTaskList +# Informationen über Operationen der File Station die im Hintergrund laufen +###################################################################################### +sub _getbackgroundTaskList { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + + # Schema: name => Name, + # opmode => operation mode, + # api => API (siehe $data{$type}{$name}{fileapi}), + # method => auszuführende API-Methode, + # params => "spezifische API-Parameter> + + my $params = { + name => $name, + opmode => "backgroundTask", + api => "BGTASK", + method => "list", + params => "", + reqtype => "GET", + header => "Accept: application/json" + }; + + addSendqueue ($params); + getApiSites ($name); + +return; +} + +###################################################################################### +# Getter remoteFolderList +# Alle freigegebenen Ordner auflisten, Dateien in einem freigegebenen Ordner +# aufzählen und detaillierte Dateiinformationen erhalten +###################################################################################### +sub _getRemoteFolderList { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $arg = $paref->{arg}; + + my $params; + + my $adds = AttrVal($name, "additionalInfo", ""); + + # Schema: name => Name, + # opmode => operation mode, + # api => API (siehe $data{$type}{$name}{fileapi}), + # method => auszuführende API-Methode, + # params => "spezifische API-Parameter> + + if ($arg) { + $arg = smUrlEncode ($arg); + my ($a,$h) = parseParams ($arg); + my $fp = $a->[0]; + my $mo = q{}; + + while (my ($k,$v) = each %$h) { # Zusatzoptionen z.B. filetype=file + $mo .= "&".$k."=".$v; + } + + $params = { + name => $name, + opmode => "remoteFolderList", + api => "LIST", + method => "list", + params => "&additional=$adds&folder_path=$fp".$mo, + reqtype => "GET" + }; + } + else { + $params = { + name => $name, + opmode => "shareList", + api => "LIST", + method => "list_share", + params => "&additional=$adds", + reqtype => "GET", + header => "Accept: application/json", + }; + } + + addSendqueue ($params); + getApiSites ($name); + +return; +} + +###################################################################################### +# Getter filefolderInfo +# Informationen über die Datei(en) erhalten +###################################################################################### +sub _getremoteFileInfo { + my $paref = shift; + my $hash = $paref->{hash}; + my $name = $paref->{name}; + my $opt = $paref->{opt}; + my $arg = $paref->{arg}; + + if(!$arg) { + return qq{The command "$opt" needs an argument !} + } + + my $params; + + my $adds = AttrVal($name, "additionalInfo", ""); + + # Schema: name => Name, + # opmode => operation mode, + # api => API (siehe $data{$type}{$name}{fileapi}), + # method => auszuführende API-Methode, + # params => "spezifische API-Parameter> + + $arg = smUrlEncode ($arg); + my ($a,$h) = parseParams ($arg); + my $fp = $a->[0]; + my $mo = q{}; + + while (my ($k,$v) = each %$h) { # Zusatzoptionen z.B. filetype=file + $mo .= "&".$k."=".$v; + } + + $params = { + name => $name, + opmode => "remoteFileInfo", + api => "LIST", + method => "getinfo", + params => "&additional=$adds&path=$fp".$mo, + reqtype => "GET", + header => "Accept: application/json" + }; + + addSendqueue ($params); + getApiSites ($name); + +return; +} + +###################################################################################### +# Getter versionNotes +###################################################################################### +sub _getversionNotes { + my $paref = shift; + + $paref->{hintextde} = \%vHintsExt_de; + $paref->{hintexten} = \%vHintsExt_en; + $paref->{notesext} = \%vNotesExtern; + + my $ret = showModuleInfo ($paref); + +return $ret; +} + +###################################################################################### +# initiale Startroutinen nach Restart FHEM +###################################################################################### +sub initOnBoot { + my $name = shift; + my $hash = $defs{$name}; + my $type = $hash->{TYPE}; + + my $ret; + + RemoveInternalTimer($name, "FHEM::SSFile::initOnBoot"); + + if ($init_done) { + my $file = $uldcache.$name; + my ($error, @content) = FileRead ($file); # Cache File der Uploads lesen wenn vorhanden + + if(!$error) { + my $json = join "", @content; + my $success = evaljson ($hash, $json); # V0.7.7 07.01.2021 + + if($success) { + $data{$type}{$name}{uploaded} = decode_json ($json); + } + else { + Log3($name, 2, qq{$name - WARNING - the content of file "$file" is not readable and may be corrupt}); + } + } + + readingsBeginUpdate($hash); + readingsBulkUpdate ($hash, "Errorcode" , "none"); + readingsBulkUpdate ($hash, "Error" , "none"); + readingsBulkUpdate ($hash, "QueueLength", 0); # Länge Sendqueue initialisieren + readingsBulkUpdate ($hash, "nextUpdate" , "undefined"); # Abrufmode initialisieren + readingsBulkUpdate ($hash, "state" , "Initialized"); # Init state + readingsEndUpdate ($hash,1); + } + else { + InternalTimer(gettimeofday()+3, "FHEM::SSFile::initOnBoot", $name, 0); + } + +return; +} + +############################################################################################# +# regelmäßiger Intervallabruf +############################################################################################# +sub periodicCall { + my $name = shift; + my $hash = $defs{$name}; + + RemoveInternalTimer($name,"FHEM::SSFile::periodicCall"); + + my $interval = AttrVal($name, "interval", 0); + + my $new; + + if(!$interval) { + $hash->{MODE} = "Manual"; + readingsSingleUpdate($hash, "nextUpdate", "manual", 1); + return; + } + else { + $new = gettimeofday()+$interval; + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "nextUpdate", "Automatic - next start Queue time: ".FmtTime($new)); # Abrufmode initial auf "Manual" setzen + readingsEndUpdate ($hash,1); + + $hash->{MODE} = "Automatic"; + } + + if(!IsDisabled($name) && ReadingsVal($name, "state", "running") ne "running") { + getApiSites($name); # Queue starten + } + + InternalTimer($new, "FHEM::SSFile::periodicCall", $name, 0); + +return; +} + +#################################################################################### +# Einstiegsfunktion Queue Abarbeitung +#################################################################################### +sub getApiSites { + my $name = shift; + my $hash = $defs{$name}; + my $addr = $hash->{SERVERADDR}; + my $port = $hash->{SERVERPORT}; + my $prot = $hash->{PROTOCOL}; + + my $type = $hash->{TYPE}; + + my ($url,$idxset,$ret); + + $hash->{HELPER}{LOGINRETRIES} = 0; + + if(!keys %{$data{$type}{$name}{sendqueue}{entries}}) { + $ret = "Sendqueue is empty. Nothing to do ..."; + Log3($name, 4, "$name - $ret"); + return $ret; + } + + if($hash->{OPMODE}) { # Überholer vermeiden wenn eine Operation läuft (V. 0.7.5" => "07.12.2020) + Log3($name, 4, qq{$name - Operation "$hash->{OPMODE} (idx: $hash->{OPIDX})" is still running. Next operation start postponed}); + return; + } + + # den nächsten Eintrag aus "SendQueue" selektieren und ausführen wenn nicht forbidSend gesetzt ist + for my $idx (sort{$a<=>$b} keys %{$data{$type}{$name}{sendqueue}{entries}}) { + if (!$data{$type}{$name}{sendqueue}{entries}{$idx}{forbidSend}) { + $hash->{OPIDX} = $idx; + $hash->{OPMODE} = $data{$type}{$name}{sendqueue}{entries}{$idx}{opmode}; + $idxset = 1; + last; + } + } + + if(!$idxset) { + $ret = qq{Only entries with "forbidSend" are in Sendqueue. Escaping ...}; + Log3($name, 4, "$name - $ret"); + return $ret; + } + + Log3($name, 4, "$name - ####################################################"); + Log3($name, 4, "$name - ### start Synology File operation $hash->{OPMODE} "); + Log3($name, 4, "$name - ####################################################"); + + readingsBeginUpdate ($hash); + readingsBulkUpdate ($hash, "state", "running"); + readingsEndUpdate ($hash, 1); + + if ($hash->{OPMODE} eq "apiInfo") { + $data{$type}{$name}{fileapi}{PARSET} = 0; # erzwinge Abruf API + } + + if ($data{$type}{$name}{fileapi}{PARSET}) { # API-Hashwerte sind bereits gesetzt -> Abruf überspringen + Log3($name, 4, "$name - API hash values already set - ignore get apisites"); + return checkSID($name); + } + + my $timeout = AttrVal($name, "timeout", 20); + Log3($name, 5, "$name - HTTP-Call will be done with timeout: $timeout s"); + + # API initialisieren und abrufen + #################################### + $data{$type}{$name}{fileapi} = apistatic ("file"); # API Template im HELPER instanziieren + + Log3 ($name, 4, "$name - API imported:\n".Dumper $data{$type}{$name}{fileapi}); + + my @ak; + for my $key (keys %{$data{$type}{$name}{fileapi}}) { + next if($key =~ /^PARSET$/x); + push @ak, $data{$type}{$name}{fileapi}{$key}{NAME}; + } + my $apis = join ",", @ak; + + my $fileapi = $data{$type}{$name}{fileapi}; + + $url = "$prot://$addr:$port/webapi/$data{$type}{$name}{fileapi}{INFO}{PATH}?". + "api=$data{$type}{$name}{fileapi}{INFO}{NAME}". + "&method=Query". + "&version=$data{$type}{$name}{fileapi}{INFO}{VER}". + "&query=$apis"; + + Log3($name, 4, "$name - Call-Out: $url"); + + my $param = { + url => $url, + timeout => $timeout, + hash => $hash, + method => "GET", + header => "Accept: application/json", + callback => \&FHEM::SSFile::getApiSites_parse + }; + + HttpUtils_NonblockingGet ($param); + +return; +} + +#################################################################################### +# Auswertung Abruf apisites +#################################################################################### +sub getApiSites_parse { ## no critic 'complexity' + my ($param, $err, $myjson) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $opmode = $hash->{OPMODE}; + + my $type = $hash->{TYPE}; + + my ($error,$errorcode,$success); + + if ($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist + Log3($name, 2, "$name - ERROR message: $err"); + + setReadingErrorState ($hash, $err); + checkSendRetry ($name, 1, $queueStartFn); + return; + } + elsif ($myjson ne "") { + ($success) = evaljson($hash,$myjson); + + if (!$success) { + Log3 ($name, 4, "$name - Data returned: ".$myjson); + checkSendRetry ($name, 1, $queueStartFn); + return; + } + + my $jdata = decode_json($myjson); + + Log3($name, 5, "$name - JSON returned: ". Dumper $jdata); + + $success = $jdata->{'success'}; + + if ($success) { + my $completed = completeAPI ($jdata, $data{$type}{$name}{fileapi}); # übergibt Referenz zum instanziierten API-Hash + + if(!$completed) { + $errorcode = "9001"; + $error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + + setReadingErrorState ($hash, $error, $errorcode); + Log3($name, 2, "$name - ERROR - $error"); + + checkSendRetry ($name, 1, $queueStartFn); + return; + } + + # Downgrades für nicht kompatible API-Versionen. Hier nur nutzen wenn API zentral downgraded werden soll + Log3($name, 4, "$name - ------- Begin of adaption section -------"); + + my @sims; + + push @sims, "LIST:1"; + push @sims, "UPLOAD:2"; + + for my $esim (@sims) { + my($k,$v) = split ":", $esim; + $data{$type}{$name}{fileapi}{$k}{VER} = $v; + $data{$type}{$name}{fileapi}{$k}{MOD} = "yes"; + Log3($name, 4, "$name - Version of $data{$type}{$name}{fileapi}{$k}{NAME} adapted to: $data{$type}{$name}{fileapi}{$k}{VER}"); + } + + Log3($name, 4, "$name - ------- End of adaption section -------"); + + setReadingErrorNone($hash, 1); + + Log3 ($name, 4, "$name - API completed:\n".Dumper $data{$type}{$name}{fileapi}); + + if ($opmode eq "apiInfo") { # API Infos in Popup anzeigen + showAPIinfo ($hash, $data{$type}{$name}{fileapi}); # übergibt Referenz zum instanziierten API-Hash) + readingsSingleUpdate ($hash, "state", "done", 1); + checkSendRetry ($name, 0, $queueStartFn); + return; + } + } + else { + $errorcode = "806"; + $error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + + readingsBeginUpdate ($hash); + readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode); + readingsBulkUpdateIfChanged ($hash, "Error", $error); + readingsBulkUpdate ($hash, "state", "Error"); + readingsEndUpdate ($hash, 1); + + Log3($name, 2, "$name - ERROR - the API-Query couldn't be executed successfully"); + + checkSendRetry ($name, 1, $queueStartFn); + return; + } + } + +return checkSID($name); +} + +############################################################################################# +# Ausführung Operation +############################################################################################# +sub execOp { + my $name = shift; + my $hash = $defs{$name}; + my $prot = $hash->{PROTOCOL}; + my $addr = $hash->{SERVERADDR}; + my $port = $hash->{SERVERPORT}; + my $sid = $hash->{HELPER}{SID}; + + my $type = $hash->{TYPE}; + + my $idx = $hash->{OPIDX}; + my $opmode = $hash->{OPMODE}; + my $method = $data{$type}{$name}{sendqueue}{entries}{$idx}{method}; + my $api = $data{$type}{$name}{sendqueue}{entries}{$idx}{api}; + my $params = $data{$type}{$name}{sendqueue}{entries}{$idx}{params}; + my $reqtype = $data{$type}{$name}{sendqueue}{entries}{$idx}{reqtype}; + my $header = $data{$type}{$name}{sendqueue}{entries}{$idx}{header}; + my $postdata = $data{$type}{$name}{sendqueue}{entries}{$idx}{postdata}; + my $toutdef = $data{$type}{$name}{sendqueue}{entries}{$idx}{timeout} // 20; + my $fileapi = $data{$type}{$name}{fileapi}; + + my ($url,$param,$error,$errorcode,$content); + + my $timeout = AttrVal($name, "timeout", $toutdef); + + Log3($name, 4, "$name - start SendQueue entry index \"$idx\" ($hash->{OPMODE}) for operation."); + Log3($name, 5, "$name - HTTP-Call will be done with timeout: $timeout s"); + + $param = { + timeout => $timeout, + hash => $hash, + method => $reqtype, + header => $header, + callback => \&FHEM::SSFile::execOp_parse + }; + + if($reqtype eq "GET") { + $url = "$prot://$addr:$port/webapi/".$fileapi->{$api}{PATH}."?api=".$fileapi->{$api}{NAME}."&version=".$fileapi->{$api}{VER}."&method=$method".$params."&_sid=$sid"; + + logUrl ($name, $url); + + $param->{url} = $url; + } + + if($reqtype eq "POST") { + $url = "$prot://$addr:$port/webapi/".$fileapi->{$api}{PATH}."?api=".$fileapi->{$api}{NAME}."&version=".$fileapi->{$api}{VER}."&method=$method&_sid=$sid"; + + logUrl ($name, $url); + + my $lclFile = $data{$type}{$name}{sendqueue}{entries}{$idx}{lclFile}; + + Log3($name, 5, "$name - POST data (string will be replaced with content of $lclFile):\n$postdata"); + + ($errorcode, $content) = slurpFile ($name, $lclFile); + + if($errorcode) { + $error = expErrors ($hash, $errorcode ); + setReadingErrorState ($hash, $error, $errorcode ); + checkSendRetry ($name, 1, $queueStartFn); + undef $content; + return; + } + + $postdata =~ s//$content/xs; + + $param->{url} = $url; + $param->{data} = $postdata; + } + + HttpUtils_NonblockingGet ($param); + + undef $content; + undef $postdata; + +return; +} + +############################################################################################# +# Callback from execOp +############################################################################################# +sub execOp_parse { + my $param = shift; + my $err = shift; + my $myjson = shift; + my $hash = $param->{hash}; + my $head = $param->{httpheader}; + my $name = $hash->{NAME}; + my $prot = $hash->{PROTOCOL}; + my $addr = $hash->{SERVERADDR}; + my $port = $hash->{SERVERPORT}; + my $opmode = $hash->{OPMODE}; + + my $type = $hash->{TYPE}; + + my ($jdata,$success,$error,$errorcode,$cherror); + + if ($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist + Log3($name, 2, "$name - ERROR message: $err"); + + $errorcode = "none"; + $errorcode = "800" if($err =~ /:\smalformed\sor\sunsupported\sURL$/xs); + + readingsBeginUpdate ($hash); + readingsBulkUpdateIfChanged ($hash, "Error", $err); + readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode); + readingsBulkUpdate ($hash, "state", "Error"); + readingsEndUpdate ($hash,1); + + checkSendRetry ($name, 1, $queueStartFn); + return; + } + elsif ($myjson ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) + if($opmode ne "download") { + $success = evaljson ($hash, $myjson); + + if (!$success) { + Log3 ($name, 4, "$name - Data returned: ".$myjson); + checkSendRetry ($name, 1, $queueStartFn); + return; + } + + eval { $jdata = decode_json($myjson); }; ## no critic 'eval not tested' #Forum: https://forum.fhem.de/index.php/topic,115371.msg1158531.html#msg1158531 + + Log3($name, 5, "$name - JSON returned: ". Dumper $jdata); + + $success = $jdata->{'success'}; + } + else { # Opmode download bringt File kein JSON + $success = 1; + $jdata = $myjson; + } + + if ($success) { + my %ra; # Hash für Ergebnisreadings + my $ret = q{}; + + $error = "none"; + my $state = "done"; + + my $params = { + hash => $hash, + param => $param, + jdata => $jdata, + href => \%ra, + }; + + if($hmodep{$opmode} && defined &{$hmodep{$opmode}{fn}}) { + $ret = &{$hmodep{$opmode}{fn}} ($params) // q{}; + undef $params; + } + else { + Log3($name, 1, qq{$name - ERROR - no operation parse function found for "$opmode"}); + checkSendRetry ($name, 0, $queueStartFn); + return; + } + + _createReadings ($name, $ret, \%ra); + } + else { # die API-Operation war fehlerhaft + Log3 ($name, 5, "$name - Header returned:\n".$head); + + $errorcode = $jdata->{error}->{code}; + $cherror = $jdata->{error}->{errors}; + $error = expErrors($hash,$errorcode) // q{}; # Fehlertext zum Errorcode ermitteln + + if ($error =~ /not found/) { + $error .= " New error: ".($cherror // "' '"); + } + + setReadingErrorState ($hash, $error, $errorcode); + + Log3($name, 2, "$name - ERROR - Operation $opmode was not successful. Errorcode: $errorcode - $error"); + + checkSendRetry ($name, 1, $queueStartFn); + } + + undef $myjson; + undef $jdata; + } + +return; +} + +############################################################################################# +# erstellt Readings aus einem übergebenen Reading Hashref ($href) +############################################################################################# +sub _createReadings { + my $name = shift; + my $ret = shift; + my $href = shift; + + my $hash = $defs{$name}; + my $type = $hash->{TYPE}; + + my $error = "none"; + my $errorcode = "none"; + my $state = "done"; + + if($ret) { + $errorcode = $ret; + $error = expErrors($hash,$errorcode); # Fehlertext zum Errorcode ermitteln + $state = "Error"; + } + + my $opmode = $hash->{OPMODE}; + my $evt = $hmodep{$opmode}{doevt}; + + readingsBeginUpdate ($hash); + + $data{$type}{$name}{lastUpdate} = FmtDateTime($hash->{".updateTime"}); # letzte Updatezeit speichern + + while (my ($k, $v) = each %$href) { + readingsBulkUpdate ($hash, $k, $v); + } + readingsEndUpdate ($hash, $evt); + + readingsBeginUpdate ($hash); + readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode ); + readingsBulkUpdateIfChanged ($hash, "Error", $error ); + readingsBulkUpdate ($hash, "lastUpdate", FmtDateTime(time)); + readingsBulkUpdate ($hash, "state", $state ); + readingsEndUpdate ($hash, 1); + + delReadings($name,1) if($data{$type}{$name}{lastUpdate}); # Readings löschen wenn Timestamp nicht "lastUpdate" + + checkSendRetry ($name, 0, $queueStartFn); + +return; +} + +############################################################################################# +# File Station Info parsen +############################################################################################# +sub _parsefilestationInfo { + my $paref = shift; + my $hash = $paref->{hash}; + my $jdata = $paref->{jdata}; + my $href = $paref->{href}; + + my $name = $hash->{NAME}; + + my $is_manager = jboolmap ($jdata->{data}{is_manager}); + my $hostname = $jdata->{data}{hostname}; + my $svfs = jboolmap ($jdata->{data}{support_vfs}); + my $sfr = jboolmap ($jdata->{data}{support_file_request}); + my $sshare = jboolmap ($jdata->{data}{support_sharing}); + my $eisom = jboolmap ($jdata->{data}{support_virtual}{enable_iso_mount}); + my $remom = jboolmap ($jdata->{data}{support_virtual}{enable_remote_mount}); + my $uid = $jdata->{data}{uid}; + my $cp = $jdata->{data}{system_codepage}; + my $sproto = join ",", @{$jdata->{data}{support_virtual_protocol}}; + + $href->{IsUserManager} = $is_manager; + $href->{Hostname} = $hostname; + $href->{Support_vfs} = $svfs; + $href->{Support_filerequest} = $sfr; + $href->{Support_sharing} = $sshare; + $href->{Support_protocols} = $sproto; + $href->{UID} = $uid; + $href->{SystemCodepage} = $cp; + $href->{Enabled_iso_mount} = $eisom; + $href->{Enabled_remote_mount} = $remom; + +return; +} + +############################################################################################# +# File Station laufende Background Tasks parsen +############################################################################################# +sub _parsebackgroundTaskList { + my $paref = shift; + my $hash = $paref->{hash}; + my $jdata = $paref->{jdata}; + my $href = $paref->{href}; + + my $name = $hash->{NAME}; + + my $total = $jdata->{data}{total}; + my $tasks = join ",\n", @{$jdata->{data}{tasks}}; + + $tasks = $tasks ? $tasks : "no running tasks"; + + $href->{BackgroundTasksNum} = $total; + $href->{BackgroundTasks} = $tasks; + +return; +} + +############################################################################################# +# File / Folder Info parsen Intro +############################################################################################# +sub _parseFiFo { + my $paref = shift; + my $hash = $paref->{hash}; + my $jdata = $paref->{jdata}; + my $href = $paref->{href}; # Hash Referenz für Ergebnisreadings + + my $name = $hash->{NAME}; + my $opmode = $hash->{OPMODE}; + + my $qal; + + if($opmode eq "shareList") { + $qal = "shares"; + } + elsif ($opmode eq "remoteFolderList") { + $qal = "files"; + } + elsif ($opmode eq "remoteFileInfo") { + $qal = "files"; + } + else { + Log3($name, 1, "$name - ERROR - no valid operation mode set for function ".(caller(0))[3]); + return; + } + + my $total = $jdata->{data}{total} // $jdata->{data}{$qal} ? scalar(@{$jdata->{data}{$qal}}) : 0; + my $len = length $total; + + my $params = { + name => $name, + jdata => $jdata, + total => $total, + len => $len, + qal => $qal, + href => $href + }; + + my $ec = __createKeyValueHash ($params); + + return $ec if($ec); + +return; +} + +############################################################################################# +# File / Folder Info parsen +############################################################################################# +sub __createKeyValueHash { + my $paref = shift; + my $name = $paref->{name}; + my $jdata = $paref->{jdata}; + my $total = $paref->{total}; + my $len = $paref->{len}; + my $qal = $paref->{qal}; + my $href = $paref->{href}; + + my $adds = AttrVal($name, "additionalInfo", ""); + + for (my $i=0; $i<$total; $i++) { + if($jdata->{data}{$qal}[$i]{code}) { + return ($jdata->{data}{$qal}[$i]{code}); # File not found bei "getinfo" File + } + + $href->{sprintf("%0$len.0f", $i+1)."_IsDir"} = jboolmap ($jdata->{data}{$qal}[$i]{isdir}) if(defined $jdata->{data}{$qal}[$i]{isdir}); + $href->{sprintf("%0$len.0f", $i+1)."_Path"} = encode("utf8", $jdata->{data}{$qal}[$i]{path}) if(defined $jdata->{data}{$qal}[$i]{path}); + $href->{sprintf("%0$len.0f", $i+1)."_Path_real"} = encode("utf8", $jdata->{data}{$qal}[$i]{additional}{real_path}) if(defined $jdata->{data}{$qal}[$i]{additional}{real_path}); + $href->{sprintf("%0$len.0f", $i+1)."_Type"} = $jdata->{data}{$qal}[$i]{additional}{type} if($jdata->{data}{$qal}[$i]{additional}{type}); + $href->{sprintf("%0$len.0f", $i+1)."_MBytes_total"} = int $jdata->{data}{$qal}[$i]{additional}{volume_status}{totalspace} / $mbf if(defined $jdata->{data}{$qal}[$i]{additional}{volume_status}{totalspace}); + $href->{sprintf("%0$len.0f", $i+1)."_MBytes_free"} = int $jdata->{data}{$qal}[$i]{additional}{volume_status}{freespace} / $mbf if(defined $jdata->{data}{$qal}[$i]{additional}{volume_status}{freespace}); + $href->{sprintf("%0$len.0f", $i+1)."_MBytes_Size"} = sprintf("%0.3f", $jdata->{data}{$qal}[$i]{additional}{size} / $mbf) if(defined $jdata->{data}{$qal}[$i]{additional}{size}); + $href->{sprintf("%0$len.0f", $i+1)."_Readonly"} = jboolmap ($jdata->{data}{$qal}[$i]{additional}{volume_status}{readonly}) if(defined $jdata->{data}{$qal}[$i]{additional}{volume_status}{readonly}); + $href->{sprintf("%0$len.0f", $i+1)."_MountpointType"} = $jdata->{data}{$qal}[$i]{additional}{mount_point_type} if(defined $jdata->{data}{$qal}[$i]{additional}{mount_point_type}); + + if($jdata->{data}{$qal}[$i]{additional}{perm}{acl}) { + $href->{sprintf("%0$len.0f", $i+1)."_Perm_Posix"} = $jdata->{data}{$qal}[$i]{additional}{perm}{posix}; + $href->{sprintf("%0$len.0f", $i+1)."_Perm_Share"} = $jdata->{data}{$qal}[$i]{additional}{perm}{share_right}; + $href->{sprintf("%0$len.0f", $i+1)."_Is_ACL_Mode"} = jboolmap ($jdata->{data}{$qal}[$i]{additional}{perm}{is_acl_mode}); + + my @acl; + while (my ($k,$v) = each %{$jdata->{data}{$qal}[$i]{additional}{perm}{acl}}) { + push @acl, "$k:$v"; + } + $href->{sprintf("%0$len.0f", $i+1)."_Perm_ACL"} = join ", ", @acl; + } + + if($jdata->{data}{$qal}[$i]{additional}{time}) { + $href->{sprintf("%0$len.0f", $i+1)."_Time_modified"} = FmtDateTime ($jdata->{data}{$qal}[$i]{additional}{time}{mtime}); + $href->{sprintf("%0$len.0f", $i+1)."_Time_changed"} = FmtDateTime ($jdata->{data}{$qal}[$i]{additional}{time}{ctime}); + $href->{sprintf("%0$len.0f", $i+1)."_Time_accessed"} = FmtDateTime ($jdata->{data}{$qal}[$i]{additional}{time}{atime}); + $href->{sprintf("%0$len.0f", $i+1)."_Time_created"} = FmtDateTime ($jdata->{data}{$qal}[$i]{additional}{time}{crtime}); + } + + if($jdata->{data}{$qal}[$i]{additional}{owner}) { + $href->{sprintf("%0$len.0f", $i+1)."_Owner_GID"} = $jdata->{data}{$qal}[$i]{additional}{owner}{gid}; + $href->{sprintf("%0$len.0f", $i+1)."_Owner_User"} = $jdata->{data}{$qal}[$i]{additional}{owner}{user}; + $href->{sprintf("%0$len.0f", $i+1)."_Owner_Group"} = $jdata->{data}{$qal}[$i]{additional}{owner}{group}; + $href->{sprintf("%0$len.0f", $i+1)."_Owner_UID"} = $jdata->{data}{$qal}[$i]{additional}{owner}{uid}; + } + + if($adds) { + $href->{sprintf("%0$len.0f", $i+1)."__----------------------"} = "--------------------------------------------------------------------"; + } + } + + $href->{EntriesTotalNum} = $total; + +return; +} + +############################################################################################# +# File Station File Download parse +############################################################################################# +sub _parseDownload { + my $paref = shift; + my $hash = $paref->{hash}; + my $jdata = $paref->{jdata}; + my $param = $paref->{param}; + my $href = $paref->{href}; + my $head = $param->{httpheader}; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $idx = $hash->{OPIDX}; + + my $obj = urlDecode ((split "=", $data{$type}{$name}{sendqueue}{entries}{$idx}{params})[1]); + + Log3 ($name, 5, "$name - Header returned:\n".$head); + + if($head =~ /404\sNot\sFound/xms) { + Log3 ($name, 2, qq{$name - ERROR - Object $obj not found for download}); + return 9002; # return Errorcode + } + + if($head =~ /400\sBad\sRequest/xms) { + Log3 ($name, 2, qq{$name - ERROR - Object $obj - Bad Request}); + return 9003; # return Errorcode + } + + my $err; + my $sp = q{}; + my $dest = urlDecode ($data{$type}{$name}{sendqueue}{entries}{$idx}{dest}); + + open my $fh, '>', $dest or do { $err = qq{Can't open file "$dest": $!}; + Log3($name, 2, "$name - $err"); + return 417; # return Errorcode + }; + + if(!$err) { + binmode $fh; + print $fh $jdata; + close $fh; + } + + $href->{LocalFile} = $dest; + + Log3 ($name, 3, qq{$name - Object $obj downloaded to "$dest"}); + +return; +} + +############################################################################################# +# File Station File Upload parse +############################################################################################# +sub _parseUpload { + my $paref = shift; + my $hash = $paref->{hash}; + my $jdata = $paref->{jdata}; + my $href = $paref->{href}; # Hash Referenz für Ergebnisreadings + my $param = $paref->{param}; + my $head = $param->{httpheader}; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $idx = $hash->{OPIDX}; + + Log3 ($name, 5, "$name - Header returned:\n".$head); + + my $skip = jboolmap ($jdata->{data}{blSkip}); + my $file = $jdata->{data}{file}; + + $href->{FileWriteSkipped} = $skip; + $href->{PID} = $jdata->{data}{pid}; + $href->{Progress} = $jdata->{data}{progress}; + $href->{RemoteFile} = encode("utf8", $file); + + my $lclobj = $data{$type}{$name}{sendqueue}{entries}{$idx}{lclFile}; # lokales File-Objekt des aktuellen Index + my $remobj = $data{$type}{$name}{sendqueue}{entries}{$idx}{remFile}; # File-Objekt im Zielverezichnis + my $trtxt; + + if($skip eq "false") { + $data{$type}{$name}{uploaded}{"$lclobj"} = { remobj => $remobj, done => 1, ts => time }; # Status und Zeit des Objekt-Upload speichern + Log3 ($name, 4, qq{$name - Object "$lclobj" uploaded}); + $trtxt = qq{Uploaded: local File "$lclobj" to remote File "$remobj"}; + } + else { + Log3 ($name, 3, qq{$name - Object "$remobj" already exists -> upload skipped}); + $trtxt = qq{Upload: skipped upload local File "$lclobj"}; + } + + CommandTrigger(undef, "$name $trtxt"); + +return; +} + +############################################################################################# +# delete Objekt parse Funktion +# $jdata: decodierte received JSON Data +# $href: Referenz zum Hash für erstellte Readings +############################################################################################# +sub _parsedeleteRemoteObject { + my $paref = shift; + my $hash = $paref->{hash}; + my $jdata = $paref->{jdata}; + my $href = $paref->{href}; + + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $idx = $hash->{OPIDX}; + + my $obj = urlDecode ((split "path=", $data{$type}{$name}{sendqueue}{entries}{$idx}{params})[1]); + + Log3 ($name, 3, qq{$name - remote Object $obj deleted}); + + $href->{deletedRemoteFile} = $obj; + +return; +} + +############################################################################################# +# check SID +############################################################################################# +sub checkSID { + my $name = shift; + my $hash = $defs{$name}; + my $type = $hash->{TYPE}; + + # SID holen bzw. login + my $subref = \&execOp; + + if(!$hash->{HELPER}{SID}) { + Log3 ($name, 3, "$name - no session ID found - get new one"); + login ($hash, $data{$type}{$name}{fileapi}, $subref, $name, $splitstr); + return; + } + +return execOp($name); +} + +############################################################################################# +# zu sendende URL ins Log schreiben +############################################################################################# +sub logUrl { + my $name = shift; + my $url = shift; + my $hash = $defs{$name}; + my $sid = $hash->{HELPER}{SID}; + + if(AttrVal($name, "showPassInLog", 0)) { + Log3($name, 4, "$name - Call-Out: $url"); + } + else { + $url =~ s/$sid//x; + Log3($name, 4, "$name - Call-Out: $url"); + } + +return; +} + +############################################################################################# +# erstelle einen Part für POST multipart/form-data +# +# $seq: first - den ersten CRLF imBody nicht schreiben wegen HttpUtils Problem: +# https://forum.fhem.de/index.php/topic,115156.0.html +# last - Ergänzung des letzten Boundary +# +############################################################################################# +sub addBodyPart { + my $cdisp = shift; + my $val = shift; + my $seq = shift // ""; # Sequence: first, last + + my $part; + + $part .= "\r\n" if($seq ne "first"); + $part .= "--".$bound; + $part .= "\r\n"; + $part .= $cdisp; + $part .= "\r\n"; + $part .= "\r\n"; + $part .= $val; + $part .= "\r\n--".$bound."-- " if($seq eq "last"); + +return $part; +} + +############################################################################################# +# File::Find Explore Files & Directories +# -M File Operator: Skript-Startzeit minus Datei-Änderungszeit, in Tagen +############################################################################################# +sub exploreFiles { + my $paref = shift; + my $allref = $paref->{allref}; + my $name = $paref->{name}; + my $mode = $paref->{mode}; # Backup/Upload-Mode + my $hash = $paref->{hash}; + my $type = $hash->{TYPE}; + + my $lu = ReadingsVal($name, "lastUpdate", ""); + + my $excl = AttrVal($name, "excludeFromUpload", ""); # excludierte Objekte (Regex) + $excl =~ s/[\r\n]//gx; + my @aexcl = split /,/xms, $excl; + push @aexcl, $excluplddef; + $excl = join "|", @aexcl; + + my $crt = time; # current runtime + + ($mode, my $d) = split ":", $mode; # $d = Anzahl Tage bei Mode= nth:X + + my $found; + my $sn = 0; + for my $obj (@$allref) { # Objekte und Inhalte der Ordner auslesen für add Queue + find ( { wanted => sub { my $file = $File::Find::name; + my $dir = $File::Find::dir; + + if("$file" =~ m/^$excl$/xs) { # File excludiert from Upload + Log3 ($name, 3, qq{$name - Object "$file" is excluded from Upload}); + return; + } + + if($mode eq "inc" && $data{$type}{$name}{uploaded}{"$file"}{done}) { + my $elapsed = ($crt - $data{$type}{$name}{uploaded}{"$file"}{ts})/86400; # verstrichene Zeit seit dem letzten Upload des Files in Tagen + return if($elapsed < ($crt-(stat($file))[9])/86400); + } + + if($mode eq "nth") { + return if(-M $file > $d); + } + + if(-f $file && -r $file) { + $sn++; + $dir =~ s/^\.//x; + $dir =~ s/\/$//x; + $found->{$sn}{lfile} = "$file"; + $found->{$sn}{ldir} = "$dir"; + $found->{$sn}{mtime} = (stat $file)[9]; + $found->{$sn}{crtime} = (stat $file)[10]; + } + }, + no_chdir => 1 + }, $obj + ); + } + +return $found; +} + +#################################################################################################### +# Abbruchroutine BlockingCall +#################################################################################################### +sub blockingTimeout { + my ($hash,$cause) = @_; + my $name = $hash->{NAME}; + + $cause = $cause // "Timeout: process terminated"; + Log3 ($name, 1, "$name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause"); + + setReadingErrorState ($hash, $cause); + + delete($hash->{HELPER}{RUNNING_PID}); + + checkSendRetry ($name, 0, $queueStartFn); + +return; +} + +############################################################################################# +# liefert die Informationen der bisherigen Uploads +############################################################################################# +sub listUploadsDone { + my $hash = shift; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + if (!keys %{$data{$type}{$name}{uploaded}}) { + return qq{No uploads have been made so far.}; + } + + my $out = ""; + $out .= "
Date & Time of last successful Uploads done to Synology Diskstation
"; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + + my $i = 0; + for my $idx (sort keys %{$data{$type}{$name}{uploaded}}) { + my $ds = $data{$type}{$name}{uploaded}{"$idx"}{done}; + next if(!$ds); + + my $ts = $data{$type}{$name}{uploaded}{"$idx"}{ts}; + my $ro = $data{$type}{$name}{uploaded}{"$idx"}{remobj}; + + $ds = "success"; + $ts = FmtDateTime($ts); + + if ($i & 1) { # $i ist ungerade + $out .= ""; + } + else { + $out .= ""; + } + $i++; + + $out .= ""; + $out .= ""; + $out .= ""; + $out .= ""; + } + + $out .= ""; + $out .= "
local Object remote Object Date / Time
$idx $ro $ts
"; + $out .= "
"; + $out .= ""; + +return $out; +} + +1; + +=pod +=item summary Module to integrate Synology File Station +=item summary_DE Modul zur Integration der Synology File Station + +=begin html + + +

SSFile

+
    + This module is used to integrate the Synology File Station into FHEM. + The SSFile module is based on functions of the Synology File Station API.

    + + The connection to the Synology server is done via a session ID after successful login. Requests/queries of the server + are stored internally in a queue and processed sequentially. If the server is temporarily unavailable, + the stored queries are processed as soon as the connection to the server is available again.

    + + Preparation

    + +
      + As a basic requirement, the Synology File Station Package must be installed on the disk station.
      + The credentials of the used user are later assigned to the created device via a set credentials command. + created. +

      +
    + + +Definition +
      +
      + The definition is made with:

      +
        + define <Name> SSFile <ServerAddr> [<Port>] [<Protocol>]

        +
      + + The parameters describe in detail: +
      +
      + + + + + + + +
      Name the name of the new device in FHEM
      ServerAddr the IP address of the Synology DS. Note: If the DNS name is used instead of IP address, the attribute dnsServer should be set in the global Device !
      Port optional - port of Synology DS (default: 5000).
      Protocol optional - protocol to communicate with the DS, http or https (default: http).
      + +

      + + Examples: +
      +      define SynBackup SSFile 192.168.2.10 
      +      define SynBackup SSFile 192.168.2.10 5001 https 
      +      # creates a SSFile device with default port (5000/http) or https with port 5001.
      +     
      + + After defining a device, only the set command credentials is available. + This command first makes the access parameters known to the Device.

      +
    + + +Set +
      +
      + +
        + +
      • credentials <user> <password>
        + + Saves the credentials.
        + +

      • +
      + +
        + +
      • deleteUploadsDone
        + + Deletes the history of all successfully executed uploads to Synology Diskstation.
        + +

      • +
      + +
        + +
      • deleteRemoteObject "<File>[,<File>,...]" | "<Folder>[,<Folder>,...]" [<args>]
        + + Deletes the specified files or directories on Synology Diskstation. Multiple objects are to be separated by commas. + Directories are to be entered without "/" at the end. All specified objects are to be enclosed in " altogether.

        + + Optionally can be specified as <args>: +
        + +
          + + + + +
          recursive= true: Delete files within a folder recursively. (default)
          false: Delete only first level file/folder. If a folder to be deleted contains a file, an error will occur because the folder cannot be deleted directly.
          +
        +
        + + Examples:
        + set <Name> deleteRemoteObject "/backup/Carport-20200625-1147065130.jpg"
        + set <Name> deleteRemoteObject "/backup/log,/backup/cookie - old.txt"
        + set <Name> deleteRemoteObject "/backup/log/archive" recursive=false
        +

      • +
      + +
        + +
      • Download "<File>[,<File>,...]" | "<Folder>[,<Folder>,...]" [<args>]
        + + Transfers the specified file(s) or folder(s) from Synology Diskstation to the destination. + If a folder is specified, it or the contents will be saved compressed in zip format to a file. + Without further specification the source object will be stored in the FHEM root directory (usually /opt/fhem), depending on the setting of the + global attribute modpath, stored with identical name.

        + + Optionally specify: +
        + +
          + + + + + +
          dest= <filename>: the object will be saved with new name in default path
          <path/filename>: the object will be saved with new name in the given path
          <path/>: the object will be saved with original name in the specified path. Important: the path must end with a "/".
          +
        +
        + + All specified objects must be enclosed in " altogether.

        + + Examples:
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg"
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg" dest=carport.jpg
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg" dest=./log/carport.jpg
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg" dest=./log/
        + set <Name> Download "/temp/applications 2020,/backup/Carport-20200625-1147065130.jpg"
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg,/Temp/card.txt" dest=/opt/
        +

      • +
      + +
        + +
      • listQueue
        + + Shows all entries in the send queue. The queue is normally only filled for a short time, but in case of a problem it can contain + contain entries permanently. This allows an error that occurred during a polling task to be determined and assigned + be assigned.
        + +

      • +
      + +
        + +
      • listUploadsDone
        + + Displays a table with date/time, source file and destination object of all successfully executed uploads to Synology Diskstation.
        + +

      • +
      + +
        + +
      • logout
        + + The user is logged out and the session is ended with .
        + +

      • +
      + +
        + +
      • prepareDownload "<File>[,<File>,...]" | "<Folder>[,<Folder>,...]" [<args>]
        + + Identical to the "Download" command. The download of the files/folders from the Synology Diskstation is however not immediately + but the entries are only put into the send queue. + To start the transfer, the command

        . + +
          + set <Name> startQueue +
        +
        + + must be executed. +

      • +
      + +
        + +
      • prepareUpload "<File>[,<File>,...]" | "<Folder>[,<Folder>,...]" <args>
        + + Identical to the "Upload" command. However, the transfer of the files to the Synology Diskstation is not immediately + but the entries are only put into the send queue. + Finally, to start the transfer, the command

        . + +
          + set <Name> startQueue +
        +
        + + must be executed. +

      • +
      + +
        + +
      • purgeQueue
        + + Purges entries in the send queue. Several options are available depending on the situation:

        . +
          + + + + + +
          -all- deletes all entries present in the send queue
          -permError- deletes all entries that are excluded from further processing due to a permanent error
          <Index> deletes a unique entry from the send queue
          +
        + +

      • +
      + +
        + +
      • startQueue
        + + The processing of the entries in the send queue is started. For most commands, the processing of the send queue is started + is started implicitly.
        + +

      • +
      + +
        + +
      • Upload "<File>[,<File>,...]" | "<Folder>[,<Folder>,...]" <args>
        + + Transfers the specified local file(s)/folder(s) to Synology Diskstation. + In the dest argument, specify the destination directory on the Synology DiskStation. + The path of the local file(s)/folder(s) to be transferred can be specified as an absolute or relative path to the FHEM global + modpath can be specified.
        + Files and folder contents are read out in the standard including Subordner and transferred to the Destination structure receiving. + Files can contain wildcards (*.) to upload only certain files.
        + Subdirectories are created by default in the destination if they do not exist.
        + All specified objects are to be enclosed in " altogether.

        + + Mandatory arguments: +
        + +
          + + + + +
          dest= <Folder>: destination path to store the files in the Synology filesystem (the path starts with a shared folder and ends without "/")
          POSIX % wildcards can be specified.
          +
        +
        + + Optional arguments: +
        + +
          + + + + + + + + + +
          ow= true: the file will be overwritten if present in the destination path (default), false: the file will not be overwritten
          cdir= true: create parent folder(s) if not present. (default), false: do not create parent folder(s)
          mode= full: all objects except those specified in the excludeFromUpload attribute will be included (default)
          inc: only new objects and objects that have changed after the last upload will be considered
          nth:<days>: only objects newer than <days> are considered (fractional numbers are allowed, e.g. 3.6)
          struc= true: all objects including their directory structure will be stored in the destination path (default)
          false: all objects will be saved without their original directory structure in the destination path
          +
        +
        + + Examples:
        + set <Name> Upload "./text.txt" dest=/home/upload
        + set <Name> Upload "/opt/fhem/old data.txt" dest=/home/upload ow=false
        + set <Name> Upload "./archive new 2020.txt" dest=/home/upload
        + set <Name> Upload "./log" dest=/home/upload mode=inc struc=false
        + set <Name> Upload "./log/*.txt,./log/archive/fhem-2019-12*.*" dest=/home/upload mode=full
        + set <Name> Upload "./log" dest=/home/upload/%Y_%m_%d_%H_%M_%S mode=full struc=false
        + set <Name> Upload "./" dest=/home/upload mode=inc
        + set <Name> Upload "/opt/fhem/fhem.pl,./www/images/PlotToChat.png,./log/fhem-2020-10-41.log" dest=/home/upload
        +

      • +
      + +
    + + +Get +
      +
      + +
        + +
      • apiInfo
        + Gets the API information of Synology File Station and opens a popup with this information. +

      • +
      + +
        + +
      • remoteFileInfo "<File>[,<File>,...]"
        + Lists information from one or more files on Synology Diskstation separated by a comma ",". + All objects must be enclosed in " altogether. +

        + + Example:
        + get <Name> remoteFileInfo "/ApplicationBackup/export.csv,/ApplicationBackup/export_2020_09_25.csv"
        +

      • +
        +
      + +
        + +
      • remoteFolderList [<args>]
        + Lists all shared folders or files in a specified folder on Synology Diskstation and creates + detailed file information. + Without argument, all shared root folders are listed. A folder path and additional + options can be specified.

        + + + + + + + + +
        sort_direction= asc: sort ascending, desc: sort descending
        onlywritable= true: list writable shared folders, false: list writable and read-only shared folders
        limit= integer: number of files requested. 0 - show all files in a given folder (default).
        pattern= Pattern to filter files to display or file extensions. Multiple patterns can be specified separated by ",".
        filetype= file: list files only, dir: list folders only, all: list files and folders
        +
        + + Objects with spaces in their names should be enclosed in ".

        + + Examples:
        + get <name> remoteFolderList /home
        + get <Name> remoteFolderList "/home/30_house & construction"
        + get <Name> remoteFolderList "/home/30_house & construction" filetype=file limit=2
        + get <Name> remoteFolderList "/home/30_house & construction" sort_direction=desc
        + get <Name> remoteFolderList /home/lyrics pattern=doc,txt +

      • +
        +
      + +
        + +
      • storedCredentials
        + + Shows the stored user/password combination. +

      • + +
        +
      + +
        + +
      • versionNotes
        + + Shows information and help about the module. +

      • + +
        +
      +
    + + +Attributes +

    +
      + +
        + +
      • additionalInfo
        + Sets the additional properties to display when retrieving file or directory information. + +

      • +
      + +
        + +
      • excludeFromUpload
        + The entered files or directories will be excluded from upload (transfer to Synology Diskstation). + The entries are evaluated as regex. Multiple objects must be separated by commas.
        + Note: Files/directories with "@" in the name are excluded from upload by default.

        + + Example:
        + attr <name> excludeFromUpload ./FHEM/FhemUtils/cacheSSCam.*,./www/SVGcache.* + +

      • +
      + +
        + +
      • interval <seconds>
        + Automatically start the internal queue every X seconds. All entries contained in the queue at the start time. + (e.g. inserted with prepareUpload) are processed. If "0" is specified, no automatic start is executed. + (default) + +

      • +
      + +
        + +
      • loginRetries
        + Number of attempts for the initial user login.
        + (default: 3) + +

      • +
      + +
        + +
      • noAsyncFillQueue
        + + Filling the upload queue can be very time consuming depending on the number of files/folders to upload and can potentially block FHEM + potentially block. For this reason, the queue is filled in a concurrent process.
        + If only few entries are to be processed or with very fast systems can be renounced by setting this attribute the use of the additional process. + Use of the additional process can be waived.
        + (default: 0) + +

      • +
      + +
        + +
      • showPassInLog
        + + If "1", the password or SID will be shown in the log.
        + (default: 0) + +

      • +
      + +
        + +
      • timeout <seconds>
        + + Timeout for communication with the File Station API in seconds.
        + (default: 20) + +

      • +
      + +
    + +
+ +=end html +=begin html_DE + + +

SSFile

+
    + Mit diesem Modul erfolgt die Integration der Synology File Station in FHEM. + Das Modul SSFile basiert auf Funktionen der Synology File Station API.

    + + Die Verbindung zum Synology Server erfolgt über eine Session ID nach erfolgreichem Login. Anforderungen/Abfragen des Servers + werden intern in einer Queue gespeichert und sequentiell abgearbeitet. Steht der Server temporär nicht zur Verfügung, + werden die gespeicherten Abfragen abgearbeitet sobald die Verbindung zum Server wieder verfügbar ist.

    + + Vorbereitung

    + +
      + Als Grundvoraussetzung muss das Synology File Station Package auf der Diskstation installiert sein.
      + Die Zugangsdaten des verwendeten Users werden später über ein Set credentials Kommando dem angelegten Device + zugewiesen. +

      +
    + + +Definition +
      +
      + Die Definition erfolgt mit:

      +
        + define <Name> SSFile <ServerAddr> [<Port>] [<Protocol>]

        +
      + + Die Parameter beschreiben im Einzelnen: +
      +
      + + + + + + + +
      Name der Name des neuen Devices in FHEM
      ServerAddr die IP-Addresse der Synology DS. Hinweis: Wird der DNS-Name statt IP-Adresse verwendet, sollte das Attribut dnsServer im global Device gesetzt werden !
      Port optional - Port der Synology DS (default: 5000).
      Protocol optional - Protokoll zur Kommunikation mit der DS, http oder https (default: http).
      + +

      + + Beispiele: +
      +      define SynBackup SSFile 192.168.2.10 
      +      define SynBackup SSFile 192.168.2.10 5001 https 
      +      # erstellt ein SSFile-Device mit Standardport (5000/http) bzw. https mit Port 5001
      +     
      + + Nach der Definition eines Devices steht nur der set-Befehl credentials zur Verfügung. + Mit diesem Befehl werden zunächst die Zugangsparameter dem Device bekannt gemacht.

      +
    + + +Set + +
      +
      + +
        + +
      • credentials <User> <Passwort>
        + + Speichert die Zugangsdaten.
        + +

      • +
      + +
        + +
      • deleteUploadsDone
        + + Löscht die Historie aller erfolgreich ausgeführten Uploads zur Synology Diskstation.
        + +

      • +
      + +
        + +
      • deleteRemoteObject "<File>[,<File>,...]" | "<Ordner>[,<Ordner>,...]" [<args>]
        + + Löscht die angegebenen Files oder Verzeichnisse auf der Synology Diskstation. Mehrere Objekte sind durch Komma zu trennen. + Verzeichnissse sind ohne "/" am Ende einzugeben. Alle angegebenen Objekte sind insgesamt in " einzuschließen.

        + + Optional kann als <args> angegeben werden: +
        + +
          + + + + +
          recursive= true: Dateien innerhalb eines Ordners rekursiv löschen. (default)
          false: Nur erste Ebene Datei/Ordner löschen. Wenn ein zu löschender Ordner eine Datei enthält, wird ein Fehler auftreten, weil der Ordner nicht direkt gelöscht werden kann.
          +
        +
        + + Beispiele:
        + set <Name> deleteRemoteObject "/backup/Carport-20200625-1147065130.jpg"
        + set <Name> deleteRemoteObject "/backup/log,/backup/cookie - old.txt"
        + set <Name> deleteRemoteObject "/backup/log/archive" recursive=false
        +

      • +
      + +
        + +
      • Download "<File>[,<File>,...]" | "<Ordner>[,<Ordner>,...]" [<args>]
        + + Überträgt das(die) angegebene(n) File(s) oder Ordner von der Synology Diskstation zur Destination. + Ist ein Ordner angegeben, wird er bzw. der Inhalt im Zip-Format komprimiert in einer Datei gespeichert. + Ohne weitere Angaben wird das Quellobjekt im FHEM Root-Verzeichnis (üblicherweise /opt/fhem), abhängig von der Einstellung des + globalen Attributs modpath, mit identischem Namen gespeichert.

        + + Optional kann angegeben werden: +
        + +
          + + + + + +
          dest= <Filename>: das Objekt wird mit neuem Namen im default Pfad gespeichert
          <Pfad/Filename>: das Objekt wird mit neuem Namen im angegebenen Pfad gespeichert
          <Pfad/>: das Objekt wird mit ursprünglichen Namen im angegebenen Pfad gespeichert. Wichtig: der Pfad muß mit einem "/" enden.
          +
        +
        + + Alle angegebenen Objekte sind insgesamt in " einzuschließen.

        + + Beispiele:
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg"
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg" dest=carport.jpg
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg" dest=./log/carport.jpg
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg" dest=./log/
        + set <Name> Download "/Temp/Anträge 2020,/backup/Carport-20200625-1147065130.jpg"
        + set <Name> Download "/backup/Carport-20200625-1147065130.jpg,/Temp/card.txt" dest=/opt/
        +

      • +
      + +
        + +
      • listQueue
        + + Zeigt alle Einträge in der Sendequeue. Die Queue ist normalerweise nur kurz gefüllt, kann aber im Problemfall + dauerhaft Einträge enthalten. Dadurch kann ein bei einer Abrufaufgabe aufgetretener Fehler ermittelt und zugeordnet + werden.
        + +

      • +
      + +
        + +
      • listUploadsDone
        + + Zeigt eine Tabelle mit Datum/Zeit, Quelldatei und Zielobjekt aller erfolgreich ausgeführten Uploads zur Synology Diskstation.
        + +

      • +
      + +
        + +
      • logout
        + + Der User wird ausgeloggt und die Session mit beendet.
        + +

      • +
      + +
        + +
      • prepareDownload "<File>[,<File>,...]" | "<Ordner>[,<Ordner>,...]" [<args>]
        + + Identisch zum "Download" Befehl. Der Download der Files/Ordner von der Synology Diskstation wird allerdings nicht sofort + gestartet, sondern die Einträge nur in die Sendequeue gestellt. + Um die Übertragung zu starten, muß abschließend der Befehl

        + +
          + set <Name> startQueue +
        +
        + + ausgeführt werden. +

      • +
      + +
        + +
      • prepareUpload "<File>[,<File>,...]" | "<Ordner>[,<Ordner>,...]" <args>
        + + Identisch zum "Upload" Befehl. Die Übertragung der Files zur Synology Diskstation wird allerdings nicht sofort + gestartet, sondern die Einträge nur in die Sendequeue gestellt. + Um die Übertragung zu starten, muß abschließend der Befehl

        + +
          + set <Name> startQueue +
        +
        + + ausgeführt werden. +

      • +
      + +
        + +
      • purgeQueue
        + + Löscht Einträge in der Sendequeue. Es stehen verschiedene Optionen je nach Situation zur Verfügung:

        +
          + + + + + +
          -all- löscht alle in der Sendequeue vorhandenen Einträge
          -permError- löscht alle Einträge, die durch einen permanenten Fehler von der weiteren Verarbeitung ausgeschlossen sind
          <Index> löscht einen eindeutigen Eintrag der Sendequeue
          +
        + +

      • +
      + +
        + +
      • startQueue
        + + Die Abarbeitung der Einträge in der Sendequeue wird gestartet. Bei den meisten Befehlen wird die Abarbeitung der Sendequeue + implizit gestartet.
        + +

      • +
      + +
        + +
      • Upload "<File>[,<File>,...]" | "<Ordner>[,<Ordner>,...]" <args>
        + + Überträgt das(die) angegebene(n) lokalen File(s)/Ordner zur Synology Diskstation. + Im Argument dest ist das Zielverzeichnis auf der Synology Diskstation anzugeben. + Der Pfad der zu übertragenden lokalen Files/Ordner kann als absoluter oder relativer Pfad zum FHEM global + modpath angegeben werden.
        + Dateien und Ordner-Inhalte werden im Standard inklusive Subordner ausgelesen und zur Destination Struktur erhaltend übertragen. + Dateien können Wildcards (*.) enthalten um nur bestimmte Dateien hochzuladen.
        + Unterverzeichnisse werden im Standard in der Destination angelegt wenn sie nicht vorhanden sind.
        + Alle angegebenen Objekte sind insgesamt in " einzuschließen.

        + + Pflichtargumente: +
        + +
          + + + + +
          dest= <Ordner>: Zielpfad zur Speicherung der Files im Synology Filesystem (der Pfad beginnnt mit einem shared Folder und endet ohne "/")
          Es können POSIX %-Wildcards angegeben werden.
          +
        +
        + + Optionale Argumente: +
        + +
          + + + + + + + + + +
          ow= true: das File wird überschrieben wenn im Ziel-Pfad vorhanden (default), false: das File wird nicht überschrieben
          cdir= true: übergeordnete(n) Ordner erstellen, falls nicht vorhanden. (default), false: übergeordnete(n) Ordner nicht erstellen
          mode= full: alle außer im Attribut excludeFromUpload angegebenen Objekte werden berücksichtigt (default)
          inc: nur neue Objekte und Objekte die sich nach dem letzten Upload verändert haben werden berücksichtigt
          nth:<Tage>: nur Objekte neuer als <Tage> werden berücksichtigt (gebrochene Zahlen sind erlaubt, z.B. 3.6)
          struc= true: alle Objekte werden inkl. ihrer Verzeichnisstruktur im Zielpfad gespeichert (default)
          false: alle Objekte werden ohne die ursprüngliche Verzeichnisstruktur im Zielpfad gespeichert
          +
        +
        + + Beispiele:
        + set <Name> Upload "./text.txt" dest=/home/upload
        + set <Name> Upload "/opt/fhem/old data.txt" dest=/home/upload ow=false
        + set <Name> Upload "./Archiv neu 2020.txt" dest=/home/upload
        + set <Name> Upload "./log" dest=/home/upload mode=inc struc=false
        + set <Name> Upload "./log/*.txt,./log/archive/fhem-2019-12*.*" dest=/home/upload mode=full
        + set <Name> Upload "./log" dest=/home/upload/%Y_%m_%d_%H_%M_%S mode=full struc=false
        + set <Name> Upload "./" dest=/home/upload mode=inc
        + set <Name> Upload "/opt/fhem/fhem.pl,./www/images/PlotToChat.png,./log/fhem-2020-10-41.log" dest=/home/upload
        +

      • +
      + +
    + + +Get +
      +
      + +
        + +
      • apiInfo
        + Ruft die API Informationen der Synology File Station ab und öffnet ein Popup mit diesen Informationen. +

      • +
      + +
        + +
      • remoteFileInfo "<File>[,<File>,...]"
        + Listet Informationen von einer oder mehreren Dateien der Synology Diskstation getrennt durch ein Komma "," auf. + Alle Objekte sind insgesamt in " einzuschließen. +

        + + Beispiele:
        + get <Name> remoteFileInfo "/ApplicationBackup/export.csv,/ApplicationBackup/export_2020_09_25.csv"
        +

      • +
        +
      + +
        + +
      • remoteFolderList [<args>]
        + Listet alle freigegebenen Ordner oder Dateien in einem angegebenen Ordner der Synology Diskstation auf und erstellt + detaillierte Dateiinformationen. + Ohne Argument werden alle freigegebenen Wurzel-Ordner aufgelistet. Ein Ordnerpfad und zusätzliche + Optionen können angegeben werden.

        + + + + + + + + +
        sort_direction= asc: aufsteigend sortieren, desc: absteigend sortieren
        onlywritable= true: listet beschreibbarer freigegebener Ordner, false: auflisten beschreibbarer und schreibgeschützter freigegebener Ordner
        limit= Integer: Anzahl der angeforderten Dateien. 0 - alle Dateien in einem bestimmten Ordner zeigen (default).
        pattern= Muster zum Filtern von anzuzeigenden Dateien bzw. Dateiendungen. Mehrere Muster können durch "," getrennt angegeben werden.
        filetype= file: nur Dateien listen, dir: nur Ordner listen, all: Dateien und Ordner listen
        +
        + + Objekte mit Leerzeichen im Namen sind in " einzuschließen.

        + + Beispiele:
        + get <Name> remoteFolderList /home
        + get <Name> remoteFolderList "/home/30_Haus & Bau"
        + get <Name> remoteFolderList "/home/30_Haus & Bau" filetype=file limit=2
        + get <Name> remoteFolderList "/home/30_Haus & Bau" sort_direction=desc
        + get <Name> remoteFolderList /home/Lyrik pattern=doc,txt +

      • +
        +
      + +
        + +
      • storedCredentials
        + + Zeigt die gespeicherten User/Passwort Kombination. +

      • + +
        +
      + +
        + +
      • versionNotes
        + + Zeigt Informationen und Hilfen zum Modul. +

      • + +
        +
      +
    + + +Attribute +

    +
      + +
        + +
      • additionalInfo
        + Legt die zusätzlich anzuzeigenden Eigenschaften beim Abruf von Datei- oder Verzeichnisinformationen fest. + +

      • +
      + +
        + +
      • excludeFromUpload
        + Die eingetragenen Dateien oder Verzeichnisse werden vom Upload (Übertragung zur Synology Diskstation) ausgeschlossen. + Die Angaben werden als Regex ausgewertet. Mehrere Objekte sind durch Komma zu trennen.
        + Hinweis: Dateien/Verzeichnisse mit "@" im Namen werden per default vom Upload ausgeschlossen.

        + + Beispiel:
        + attr <Name> excludeFromUpload ./FHEM/FhemUtils/cacheSSCam.*,./www/SVGcache.* + +

      • +
      + +
        + +
      • interval <Sekunden>
        + Automatischer Start der internen Queue alle X Sekunden. Alle zum Startzeitpunkt in der Queue enthaltenen Einträge + (z.B. mit prepareUpload eingefügt) werden abgearbeitet. Ist "0" angegeben, wird kein automatischer Start + ausgeführt. (default) + +

      • +
      + +
        + +
      • loginRetries
        + Anzahl der Versuche für das inititiale User login.
        + (default: 3) + +

      • +
      + +
        + +
      • noAsyncFillQueue
        + + Das Füllen der Upload-Queue kann in Abhängigkeit der Anzahl hochzuladender Dateien/Ordner sehr zeitaufwändig sein und FHEM + potentiell blockieren. Aus diesem Grund wird die Queue in einem nebenläufigen Prozess gefüllt.
        + Sollen nur wenige Einträge verarbeitet werden oder bei sehr schnellen Systemen kann durch Setzen dieses Attributs auf die + Verwendung des zusätzlichen Prozesses verzichtet werden.
        + (default: 0) + +

      • +
      + +
        + +
      • showPassInLog
        + + Wenn "1" wird das Passwort bzw. die SID im Log angezeigt.
        + (default: 0) + +

      • +
      + +
        + +
      • timeout <Sekunden>
        + + Timeout für die Kommunikation mit der File Station API in Sekunden.
        + (default: 20) + +

      • +
      + +
    + +
+ +=end html_DE + +=for :application/json;q=META.json 57_SSFile.pm +{ + "abstract": "Integration of the Synology File Station.", + "x_lang": { + "de": { + "abstract": "Integration der Synology File Station." + } + }, + "keywords": [ + "Synology", + "Download", + "Upload", + "Backup", + "Restore", + "Filestransfer", + "File Station" + ], + "version": "v1.1.1", + "release_status": "stable", + "author": [ + "Heiko Maaz " + ], + "x_fhem_maintainer": [ + "DS_Starter" + ], + "x_fhem_maintainer_github": [ + "nasseeder1" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.014, + "POSIX": 0, + "JSON": 4.020, + "Data::Dumper": 0, + "MIME::Base64": 0, + "Time::HiRes": 0, + "HttpUtils": 0, + "Encode": 0, + "Encode::Guess": 0, + "FHEM::SynoModules::API": 0, + "FHEM::SynoModules::SMUtils": 0, + "FHEM::SynoModules::ErrCodes": 0, + "GPUtils": 0, + "File::Find": 0, + "File::Glob": 0 + }, + "recommends": { + "FHEM::Meta": 0 + }, + "suggests": { + } + } + }, + "resources": { + "x_wiki": { + "web": "https://wiki.fhem.de/wiki/SSFile_-_Integration_der_Synology_File_Station", + "title": "SSFile - Integration der Synology File Station" + }, + "repository": { + "x_dev": { + "type": "svn", + "url": "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter", + "web": "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter/50_SSFile.pm", + "x_branch": "dev", + "x_filepath": "fhem/contrib/", + "x_raw": "https://svn.fhem.de/fhem/trunk/fhem/contrib/DS_Starter/50_SSFile.pm" + } + } + } +} +=end :application/json;q=META.json + +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 3756932e0..96f0e0a9c 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -267,6 +267,7 @@ FHEM/49_TBot_List.pm viegener Unterstützende Dienste FHEM/50_HP1000.pm loredo Unterstützende Dienste/Wettermodule FHEM/50_MOBILEALERTSGW.pm MarkusF Sonstige Systeme FHEM/50_SSChatBot.pm DS_Starter Sonstiges +FHEM/50_SSFile.pm DS_Starter Sonstiges FHEM/50_TelegramBot.pm viegener Unterstützende Dienste FHEM/50_WS300.pm Dirk SlowRF FHEM/51_I2C_BH1750.pm arnoaugustin Einplatinencomputer (bitte auch PM)