CopyDirWithProgress

Veröffentliche deine funktionierenden Skripte und Funktionen

Moderator: jNizM

just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

CopyDirWithProgress

Post by just me » 23 Mar 2023, 11:54

Edit am 25.03.2023: Fehlerkorrektur für Unterverzeichnisse.

Moin,

auf Wunsch eines geschätzten langjährigen deutschen Mitglieds der AHK-Gemeinde habe ich das folgende Skript von AHK 1.0 (Basic) in die 'modernere' Welt von AHK 1.1 überführt. Es stellt eine Funktion zum Kopieren eines Verzeichnisses bereit, die mit einer eigenen Gui über den Fortschritt der Aktion informiert.
Die Funktion hat folgende Parameter:
  1. Source
    Pfad des Quellverzeichnisses mit oder ohne abschließendem Backslash \
  2. Dest
    Pfad des Zielverzeichnisses mit oder ohne abschließendem Backslash \
  3. Optional: Flag
    0 = Dateien im Zielverzeichnis nicht überschreiben
    1 = Dateien im Zielverzeichnis überschreiben
    2 = Zielverzeichnis ggf. vor dem Kopieren löschen
    Standard: 1
  4. Optional: Pattern
    Dateiauswahlmuster (z.B. "*.ahk") oder Komma-getrennte Liste von Dateierweiterungen (z.B. "ahk,ini,hlp")
    Standard: "" = "*.*"
    Bei Vorgabe eines Suchmusters werden Ordner, die keine entsprechenden Dateien enthalten, nicht übernommen!
  5. Optional: Owner
    Name, Nummer oder HWND/ID der 'Eigentümer-Gui'
Der Kopiervorgang kann jederzeit durch Schließen des Fensters oder Klicken des Buttons abgebrochen werden.
Wenn die Funktion fehlerfrei komplett durchläuft, gibt sie 1 (Wahr/True) zurück, sonst 0 (Falsch/False).

Wer Änderungswünsche hat oder eine Version für AHK v2 wünscht, der melde sich.

Code: Select all

#NoEnv
SetBatchLines, -1
Quelle := "D:\AHK_Alt"
Ziel := "D:\AHK_Sav"

MsgBox, % CopyDirWithProgress(Quelle, Ziel, 2, "ahk,ini,txt")

ExitApp
; ----------------------------------------------------------------------------------------------------------------------
; CopyDirWithProgress()
;
; Funktion:
; Kopieren von Verzeichnissen mit erweiterter Funktionalität.
; 1. Gui mit Fortschrittsanzeige
; 2. Löschen des Zielverzeichnisses vor dem Kopieren
; 3. Auswahl bestimmter Dateien mittels Suchmuster
;
; Parameter:
; Source             Quellverzeichnis
; Dest               Zielverzeichnis
; Optional: Flag     0 = Dateien im Zielverzeichnis nicht überschreiben
;                    1 = Dateien im Zielverzeichnis überschreiben
;                    2 = Zielverzeichnis ggf. vor dem Kopieren löschen
;                    Standard: 1
; Optional: Pattern  Dateiauswahlmuster (z.B. "*.ahk") oder Komma-getrennte
;                    Liste von Dateierweiterungen (z.B. "ahk,ini,hlp")
;                    Standard: "" = "*.*"
;                    Bei Vorgabe eines Suchmusters werden Ordner, die keine
;                    entsprechenden Dateien enthalten, nicht übernommen!
; Optional: Owner    Name, Nummer oder HWND/ID der 'Eigentümer-Gui'
;
; Danksagung:
; Mein spezieller Dank gilt Superfraggle, ahklerner und tonne für
; http://www.autohotkey.com/forum/post-143928.html
; ----------------------------------------------------------------------------------------------------------------------
CopyDirWithProgress(Source, Dest, Flag := 1, Pattern := "", Owner := "") {
   ; -------------------------------------------------------------------------------------------------------------------
   ; Statische Variablen
   Static Callback   := RegisterCallback(A_PtrSize = 8 ? "CopyDirWithProgress_Progress_64"
                                                       : "CopyDirWithProgress_Progress_32")
   Static DeleteDest := 2
   Static OverWrite  := 1
   ; -------------------------------------------------------------------------------------------------------------------
   ; Sprachabhängige Konstanten
   Static TXG  := {EN: " Copy"                     , DE: " Kopieren"}
   Static TXS  := {EN: "Source:`t`t"               , DE: "Quelle:`t"}
   Static TXD  := {EN: "Destination:`t"            , DE: "Ziel:`t"}
   Static TXC  := {EN: "Cancel"                    , DE: "Abbruch"}
   Static TXT  := {EN: "Total: "                   , DE: "Gesamt: "}
   Static TXS1 := {EN: "Getting files ..."         , DE: "Dateien werden ermittelt ..."}
   Static TXS2 := {EN: "File "                     , DE: "Datei "}
   Static TXS3 := {EN: " of "                      , DE: " von "}
   Static TXS4 := {EN: "Done!"                     , DE: "Fertig!"}
   Static TXS5 := {EN: "Terminated!"               , DE: "Abgebrochen!"}
   Static TXM1 := {EN: "Error while copying file " , DE: "Fehler beim Kopieren der Datei "}
   Static TXM2 := {EN: "Size of source: "          , DE: "Originalgröße: "}
   Static TXM3 := {EN: "Size of copy: "            , DE: "Größe der Kopie: "}
   Static TXM4 := {EN: "Terminate the copying?"    , DE: "Kopiervorgang abbrechen?"}
   ; -------------------------------------------------------------------------------------------------------------------
   ; Spracheinstellung
   Static LANG := (SubStr(A_Language, 3) = "07") ? "DE" : "EN"
   ; -------------------------------------------------------------------------------------------------------------------
   ; Parameter auswerten
   Source := RTrim(Source, "\")
   If !InStr(FileExist(Source), "D") {
      MsgBox, 16, %A_ThisFunc%, Ungültiges Quellverzeichnis:`n%Source%
      Return False
   }
   Dest   := RTrim(Dest, "\")
   MEXT := ""
   If (Pattern = "")
      Pattern := "*.*"
   Else If InStr(Pattern, ",") {
      MEXT := Pattern
      Pattern := "*.*"
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; Zielverzeichnis ggf. löschen?
   If (Flag = DeleteDest) {
      If (FileExist(Dest)) {
         FileRemoveDir, %Dest% , 1
         If (ErrorLevel) {
            MsgBox, 16, %A_ThisFunc%, Das Zielverzeichnis`n%Dest%`nkonnte nicht gelöscht werden!
            Return False
         }
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; Objekt für den Informationsaustausch mit der Callback-Funktion initialisieren
   CDWP := {HGUI: 0, HPGB1: 0, HPGB2: 0, TotalSize: 0, FileCopied: 0, TotalCopied: 0, Cancel: False}
   ; -------------------------------------------------------------------------------------------------------------------
   ; Gui
   TxtW := 600 ; minimale Breite
   Gui, New, +AlwaysOnTop +HwndHGUI +Toolwindow +OwnDialogs +LastFound +Owner%Owner% +Label_CDE_Gui_
   Gui, Margin, 10, 10
   Gui, Add, Text, xm hwndHCTL, % TXS[LANG] . Source
   GuiControlGet, P, Pos, %HCTL%
   TxtW := Max(TxtW, PW)
   Gui, Add, Text, xm y+5 hwndHCTL, % TXD[LANG] . Dest
   GuiControlGet, P, Pos, %HCTL%
   TxtW := Max(TxtW, PW)
   Gui, Add, Text, xm w%TxtW% hwndHTX1 +0x8000 ; SS_PATHELLIPSIS = 0x8000
   Gui, Add, Progress, xm y+3 wp hwndHPGB1 cLime, 0 ; -Smooth
   Gui, Add, Text, xm y+5 wp, % TXT[LANG]
   Gui, Add, Progress, xm y+3 wp hwndHPGB2 cLime, 0 ; -Smooth
   BtnX := 10 + TxtW - 100
   Gui, Add, Button, x%BtnX% w100 hwndHCTL g_CDE_Gui_Close, % TXC[LANG]
   GuiControlGet, P, Pos, %HBTN%
   TxtW -= PW
   Gui, Add, Text, xm yp w%TxtW% h%PH% hwndHSB +0x0200, % TXS1[LANG]
   Gui, Show, , % TXG[LANG]
   CDWP.HGUI  := HGUI
   CDWP.HPGB1 := HPGB1
   CDWP.HPGB2 := HPGB2
   ; -------------------------------------------------------------------------------------------------------------------
   ; Anzahl der Dateien und Gesamtgröße ermitteln
   StartPos := StrLen(Source) + 1
   CDWP.TotalSize := 0
   Files := 0
   Paths := []
   Index := 0
   Loop, Files, %Source%\%Pattern%, % (Pattern = "*.*" ? "DFR" : "FR")
   {
      NewFile := Dest . SubStr(A_LoopFileLongPath, StartPos)
      If (Flag = 0) && FileExist(NewFile)
         Continue
      If (MEXT) {
         If A_LoopFileExt Not In %MEXT%
            Continue
      }
      IsDir := InStr(A_LoopFileAttrib, "D")
      If !(IsDir) {
         CDWP.TotalSize += A_LoopFileSize
         Files++
      }
      Paths[++Index] := {Src: A_LoopFileLongPath, Dst: NewFile, Size: IsDir ? "D" : A_LoopFileSize}
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; Dateien kopieren
   Gui, +OwnDialogs
   CDWP.TotalCopied := 0
   CDWP.Cancel := False
   Index := 0
   LastDir := ""
   For I, File In Paths {
      NewFile := File.Dst
      If (File.Size = "D") {
         If !FileExist(NewFile) {
            FileCreateDir, %NewFile%
            If (ErrorLevel) {
               MsgBox, 16, %A_ThisFunc%, Das Verzeichnis`n%NewFile%`nkonnte nicht erstellt werden!
               Gui, %HGUI%:Destroy
               Return False
            }
         }
         LastDir := File.Dst
         Continue
      }
      GuiControl, , %HSB% , % TXS2[LANG] . ++Index . TXS3[LANG] . Files
      SplitPath, NewFile, , Dir
      If (Dir != LastDir) {
         If !FileExist(Dir) {
            FileCreateDir, %Dir%
            If (ErrorLevel) {
               MsgBox, 16, %A_ThisFunc%, Das Verzeichnis`n%Dir%`nkonnte nicht erstellt werden!
               Gui, %HGUI%:Destroy
               Return False
            }
         }
         LastDir := Dir
      }
      GuiControl, , %HTX1%, % File.Src
      GuiControl, , %HPGB1%, 0
      CDWP.FileCopied := 0
      RetVal := DllCall("CopyFileEx", "Str" , File.Src, "Str" , NewFile, "Ptr", Callback
                                    , "Ptr", &CDWP, "Ptr", 0, "UInt", 0)
      Sleep, -1
      If (CDWP.Cancel)
         Break
      If (RetVal = 0) || (ErrorLevel != 0) || (CDWP.FileCopied < File.Size) {
         MsgBox, 36, %A_ThisFunc%,  % TXM1[LANG] . NewFile . "!`n`n"
                                    . "RetVal: " . RetVal . " *`n"
                                    . "A_LastError: " . A_LastError . " *`n"
                                    . TXM2[LANG] . File.Size . " *`n"
                                    . TXM3[LANG] . CDWP.FileCopied . " *`n`n"
                                    . TXM4[LANG]
         IfMsgBox, Yes
         {
            FileDelete, %NewFile%
            Gui, %HGUI%:Destroy
            Return False
         }
      }
      CDWP.TotalCopied += File.Size
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; Fertig!
   GuiControl, , %HPGB1%, 0
   GuiControl, , %HSB%, % CDWP.Cancel ? TXS5[LANG] : TXS4[LANG]
   Sleep, 1000
   Gui, %HGUI%:Destroy
   Return % CDWP.Cancel ? False : True
   ; -------------------------------------------------------------------------------------------------------------------
   _CDE_Gui_Close:
   _CDE_Gui_Escape:
   CDWP.Cancel := True
   Return
}
; ------------------------------------------------------------------------------
; Callback-Funktion f�r die Fortschrittsanzeige - 32-Bit
CopyDirWithProgress_Progress_32(P1L, P1H, P2L, P2H, P3L, P3H, P4L, P4H, P5, P6, P7, P8, P9) {
   ; LARGE_INTEGER TotalFileSize (2 * Integer)
   ; LARGE_INTEGER TotalBytesTransferred (2 * Integer)
   ; LARGE_INTEGER StreamSize (2 * Integer)
   ; LARGE_INTEGER StreamBytesTransferred (2 * Integer)
   ; DWORD dwStreamNumber
   ; DWORD dwCallbackReason
   ; HANDLE hSourceFile
   ; HANDLE hDestinationFile
   ; LPVOID lpData
   CDWP := Object(P9)
   FileSize := (P1H << 32) + P1L
   CDWP.FileCopied := (P2H << 32) + P2L
   GuiControl, % CDWP.HGUI . ":", % CDWP.HPGB1, % Round(CDWP.FileCopied / FileSize * 100)
   GuiControl, % CDWP.HGUI . ":", % CDWP.HPGB2, % Round((CDWP.FileCopied + CDWP.TotalCopied) / CDWP.TotalSize * 100)
   Return CDWP.Cancel
}
; Callback-Funktion f�r die Fortschrittsanzeige - 64-Bit
CopyDirWithProgress_Progress_64(P1, P2, P3, P4, P5, P6, P7, P8, P9) {
   ; LARGE_INTEGER TotalFileSize
   ; LARGE_INTEGER TotalBytesTransferred
   ; LARGE_INTEGER StreamSize
   ; LARGE_INTEGER StreamBytesTransferred
   ; DWORD dwStreamNumber
   ; DWORD dwCallbackReason
   ; HANDLE hSourceFile
   ; HANDLE hDestinationFile
   ; LPVOID lpData
   CDWP := Object(P9)
   FileSize := P1
   CDWP.FileCopied := P2
   GuiControl, % CDWP.HGUI . ":", % CDWP.HPGB1, % Round(CDWP.FileCopied / FileSize * 100)
   GuiControl, % CDWP.HGUI . ":", % CDWP.HPGB2, % Round((CDWP.FileCopied + CDWP.TotalCopied) / CDWP.TotalSize * 100)
   Return CDWP.Cancel
}

Anmerkung:
Die zugehörige API-Funktion CopyFileEx() ruft eine Callback-Funktion auf, um u.a. eine Fortschrittsanzeige zu ermöglichen. Die ersten vier Parameter der Funktion sind 64-Bit Werte. Die werden mit AHK 64-Bit in einem Parameter, mit AHK 32-Bit aber in zwei Parametern übergeben. Deshalb gibt es zwei Versionen der Callback-Funktion. Alternativ wäre es möglich, die unterschiedlichen Parameter innerhalb einer Funktion unterschiedlich zu behandeln.
Last edited by just me on 25 Mar 2023, 03:36, edited 1 time in total.

User avatar
moefr01
Posts: 115
Joined: 25 Nov 2015, 09:01
Location: Germany

Re: CopyDirWithProgress

Post by moefr01 » 25 Mar 2023, 02:48

Moin just me

merci für das interessante Kopierscript... ;)
Mir ist aufgefallen, dass der Kopiervorgang mit Flag=1 und Pattern="*.*" folgendermaßen abbricht:
grafik.png
grafik.png (43.12 KiB) Viewed 1125 times
Ich nutze Windows 11 64Bit - 22H2.22621.1413

Grüße
moefr01 :wave:

just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: CopyDirWithProgress

Post by just me » 25 Mar 2023, 03:31

@moefr01: Moin und danke für den Hinweis. Das ist ein Fehler, der sich bei der Portierung eingeschlichen hat. Er kann auftreten, wenn zu kopierende Unterverzeichnisse im Zielverzeichnis vorhanden sind. Ich habe das oben korrigiert.

User avatar
moefr01
Posts: 115
Joined: 25 Nov 2015, 09:01
Location: Germany

Re: CopyDirWithProgress

Post by moefr01 » 25 Mar 2023, 05:40

@just me: Super... das mit den Unterverzeichnissen hat soweit geklappt, jedoch sind einmal gesicherte Dateien in folgender Kopieraktion nicht überschreibbar (Flag=1), wenn ursprünglich schreibgeschützt. Vielleicht wäre ein FileAttribut-Auslesen + Schreibschutz entfernen + Kopieren/Überschreiben + Schreibschutz wieder setzen ein Ansatz?
moefr01 :wave:

Post Reply

Return to “Skripte und Funktionen”