📂 Downloader für Dateien, HttpUtils_NonblockingGet() und FileWrite() ohne NL

Begonnen von Torxgewinde, 19 Dezember 2025, 10:30:02

Vorheriges Thema - Nächstes Thema

Torxgewinde

Dieser Codeschnipsel ermöglicht es eine oder auch mehrere Dateien von Webservern herunterzuladen und lokal im Dateisystem zu speichern. Wenn ein Download noch aktiv ist, während ein weiterer hinzukommt, werden solche Aufträge im Reading "backlog" vermerkt und nacheinander abgearbeitet.

In diesem Snippet ist ein Link (das Ordner-Emoji), wenn man es anklickt werden mehrere Firmwaredateien in den FhemWeb-Ordner heruntergeladen. Dafür wird FW_cmd() genutzt und auch ganz nützlich ist IMHO die Methode mit der man mehrere FHEM-Befehle auf diese Art von Javascript an FHEM übergeben kann.

Zur Nutzung:

Wie bei mir üblich, ohne viel Extra-Devices, alles schön kompakt beisammen in diesem Snippet:

defmod MyDownloader dummy
attr MyDownloader userattr Filepath
attr MyDownloader devStateIcon {\
#Lade mehrere Dinge herunter, füllt ggf. das Backlog:\
my $cmd = <<"CMD";;\
set $name job Filepath="/opt/fhem/www/tasmota.bin" https://ota.tasmota.com/tasmota/release/tasmota.bin;;\
set $name job Filepath="/opt/fhem/www/tasmota.bin.gz" https://ota.tasmota.com/tasmota/release/tasmota.bin.gz;;\
set $name job Filepath="/opt/fhem/www/tasmota-minimal.bin.gz" https://ota.tasmota.com/tasmota/release/tasmota-minimal.bin.gz;;\
set $name job Filepath="/opt/fhem/www/tasmota32-zbbrdgpro.bin" https://ota.tasmota.com/tasmota32/release/tasmota32-zbbrdgpro.bin\
CMD\
\
#verpacke das ganze sicher für den Transport als BASE64 und als RCE payload:\
$cmd = "{fhem(MIME::Base64::decode_base64('". encode_base64($cmd,'') ."'))}";;\
$cmd = urlEncode($cmd);;\
\
#Mit JS wird verhindert, dass der Browser navigiert:\
my $link = "<a href=\"#\" onclick=\"".\
           "FW_cmd(FW_root + '?XHR=1&cmd=${cmd}', function(data){log('📂: ' + data)});; return false;;".\
           "\">📂</a>";;\
\
return "<div>". ReadingsVal($name, "result", "???") ." $link</div>";;\
}
attr MyDownloader readingList job
attr MyDownloader setList job
attr MyDownloader userReadings result:job:.* {\
    my $hash = $defs{$name};;\
    my $job = ReadingsVal($name, "job", 'https://fhem.de/www/images/default/fhemicon.png');;\
    my $path = AttrVal($name, "Filepath", "/opt/fhem/www/download.bin");;\
    \
    if ($job =~ s/(?:Filepath|Dateipfad)="(.*?)"//) {\
        $path = $1;;\
    }\
    \
    # Entferne Whitespaces:\
    $job =~ s/^\s+|\s+$//g;;\
    \
    # Wenn busy aktiv ist, dann den Job ins Backlog schieben:\
    if (ReadingsVal($name, "busy", 0) != 0) {\
        my $backlog = ReadingsVal($name, "backlog", "");;\
        $backlog .= "Filepath=\"${path}\" ${job}\n";;\
        readingsBulkUpdate($hash, "backlog", $backlog, 1);;\
        return "queued job ($job)";;\
    }\
    \
    # ab hier sind wir busy mit dem request:\
    readingsBulkUpdate($hash, "busy", 1, 1);;\
    \
    # definiere Callback:\
    my $fnFct = sub {\
        my ($param, $err, $data) = @_;;\
        \
        my $len = $data ? length($data) : 0;;\
        my $fw_hash = {FileName => $path, ForceType => "file", NoNL => 1};;\
        my $error = $err ? $err : FileWrite($fw_hash, $data);;\
        \
        readingsBeginUpdate($hash);;\
        readingsBulkUpdate($hash, "result", "saved $len Bytes to $path", 1) if !$error;;\
        readingsBulkUpdate($hash, "result", "ERROR ($error|$err|$path|$job)", 1) if $error;;\
        \
        # prüfe das Backlog:\
        my $backlog = ReadingsVal($name, "backlog", "");;\
        my @lines = grep { /\S/ } split(/\r?\n/, $backlog);;\
        \
        if (@lines) {\
            my $next = shift @lines;;  # ersten Job holen\
            my $newbacklog = join("\n", @lines);;\
            readingsBulkUpdate($hash, "backlog", $newbacklog, 1);;\
            \
            # Nächsten Job starten wenn diese Funktion endet,\
            # deswegen über "@cmdList","%sleepers" in fhem.pl\
            fhem("sleep 0.0;; set $name job $next");;\
        }\
        \
        readingsBulkUpdate($hash, "busy", 0, 1);;\
        readingsEndUpdate($hash, 1);;\
    };;\
    \
    # prepare and call HttpUtils:\
    my $param = {\
        url        => $job,\
        timeout    => 10,\
        hash       => $hash,\
        method     => "GET",\
        callback   => $fnFct\
    };;\
    HttpUtils_NonblockingGet($param);;\
    \
    return "download job started ($job)";;\
}