CopyFilesEx - Threaded und Crossplattform

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

CopyFilesEx - Threaded und Crossplattform

Beitrag von ts-soft »

Ähnliches für einzelne Dateien gab es schon mal hier: http://www.purebasic.fr/german/viewtopi ... 29#p180129
Allerdings kannte ich diesen Thread bei Erstellung meiner Funktion nicht und meine bietet
auch etwas mehr.

Es können mehrere Dateien in ein Verzeichnis kopiert werden. Verzeichnisse können nicht kopiert werden.
Es werden wahlweise die Attribute und Daten (Datums?) übertragen. Die Buffergrösse kann bestimmt werden.
Das optionale Callback gibt Dateinamen der aktuellen Datei, den Zielpfad, Gesamtfortschritt in Prozent sowie
Fortschritt der aktuellen Datei wieder.

Weitere Infos im Source.
CopyFilesEx.pbi:

Code: Alles auswählen

; CopyFilesEx
; Version 1.1
; Author: Thomas (ts-soft) Schulz
; erstellt: 27.04.2010
; zuletzt geändert: 28.04.2010
; PB-Version: 4.50 und höher
; Crossplattform (mit kleinen Einschränkungen)

EnableExplicit

Prototype.i CopyFileExCallback(File.s, Dir.s, Sum.f, Procent.f)
; File erhält den Namen der aktuellen Datei
; Dir erhält den Namen des Zielverzeichnisses
; Sum ist der Gesamtforschritt in Prozent
; Procent ist der Fortschritt der aktuellen Datei

; Wenn das Callback #FALSE zurückgibt, wird der Kopiervorgang beendet, anderenfalls fortgesetzt


Structure CopyFilesEx
  hWnd.i                ; WindowID(#Window) des Fensters, das per Message benachrichtig wird, wenn der Thread fertig ist.
  List sourcefiles.s()  ; Liste der zu kopierenden Dateien (mit Pfad)
  destinationDir.s      ; Zielverzeichnis
  bufferSize.i          ; der zu verwendente Buffersize beim kopieren der Dateien. Sollte auf #PB_Default gesetzt werden (entspricht 4096)
  callback.CopyFileExCallback ; ProcedureAdresse des Callbacks für den Fortschritt
  Mutex.i               ; das Ergebnis von CreateMutex()
  pStopVar.i            ; Adresse der globalen StopVariable
  IgnoreAttribute.b     ; auf #True gesetzt, werden Attribute nicht wieder hergestellt
  IgnoreDate.b          ; auf #True gesetzt, wird Dateidatum nicht wieder hergestellt
EndStructure

; interne Funktion
Procedure.q CopyFileBuffer(sourceID.i, destID.i, buffersize.i)
  Protected *mem, result.q
  
  *mem = AllocateMemory(buffersize)
  
  If *mem And IsFile(sourceID) And IsFile(destID)
    If Loc(sourceID) + buffersize < Lof(sourceID)
      ReadData(sourceID, *mem, buffersize)
      WriteData(destID, *mem, buffersize)
      result = Loc(destID)
    Else
      buffersize = Lof(sourceID) - Loc(destID)
      If buffersize
        ReadData(sourceID, *mem, buffersize)
        WriteData(destID, *mem, buffersize)
      EndIf
      CloseFile(sourceID)
      CloseFile(destID)
      result = 0
    EndIf
    FreeMemory(*mem)
  EndIf
  
  ProcedureReturn result
EndProcedure

Procedure.i CopyFilesEx(*CFE.CopyFilesEx)
  Protected sourceID.i, destID.i, bufferSize.i, position.q, Size.q, Procent.f, cFiles.i, Sum.f, count.i
  Protected Attribute.i, Date_Created.i, Date_Accessed.i, Date_Modified.i
  
  If *CFE\Mutex
    LockMutex(*CFE\Mutex)
  EndIf
  
  cFiles = ListSize(*CFE\sourcefiles())

  With *CFE
    CompilerIf #PB_Compiler_OS = #PB_OS_Windows
      If Right(\destinationDir, 1) <> "\" : \destinationDir + "\" : EndIf ; fehlende Slashes/Backslahes hinzufügen
    CompilerElse
      If Right(\destinationDir, 1) <> "/" : \destinationDir + "/" : EndIf
    CompilerEndIf
    If \bufferSize = #PB_Default : \bufferSize = 4096 : EndIf ; standardbuffer grösse
    If \bufferSize < 1028 : \bufferSize = 1028 : EndIf ; empfohlene mindestgrösse setzen
  EndWith
  
  If FileSize(*CFE\destinationDir) = -2 ; Zielverzeichnis muß existieren
    ForEach *CFE\sourcefiles()
      With *CFE
        
        If FileSize(\sourcefiles()) >= 0 ; Dateien müssen existieren!
          Attribute = GetFileAttributes(\sourcefiles())
          Date_Created = GetFileDate(\sourcefiles(), #PB_Date_Created)
          Date_Accessed = GetFileDate(\sourcefiles(), #PB_Date_Accessed)
          Date_Modified = GetFileDate(\sourcefiles(), #PB_Date_Modified)
  
          sourceID = ReadFile(#PB_Any, \sourcefiles())
          If IsFile(sourceID) = #False : Continue : EndIf ; lesen fehlgeschlagen, fortsetzen mit nächstem File.
          FileBuffersSize(sourceID, \bufferSize)
          Size = Lof(sourceID)
          destID = CreateFile(#PB_Any, \destinationDir + GetFilePart(\sourcefiles()))
          If IsFile(destID) = #False : CloseFile(sourceID) : Continue : EndIf ; erstellen fehlgeschlagen, fortsetzen mit nächstem File.
          FileBuffersSize(destID, \bufferSize)
          Sum = (100 * count) / cFiles
          count + 1
          
          Repeat
            position = CopyFileBuffer(sourceID, destID, \bufferSize)
            If \callback <> 0
              Procent = (100 * position) / Size 
              If Not \callback(GetFilePart(\sourcefiles()), \destinationDir, Sum, Procent) Or PeekI(\pStopVar) = #True ; abbrechen gewählt
                If IsFile(sourceID) : CloseFile(sourceID) : EndIf
                If IsFile(destID) : CloseFile(destID) : EndIf
                DeleteFile(\destinationDir + GetFilePart(\sourcefiles()))
                Break
              EndIf
              If position = 0
                \callback("", "", 100, 0)
              EndIf
            EndIf
          Until position = 0
          
          If Not \IgnoreAttribute
            SetFileAttributes(\destinationDir + GetFilePart(\sourcefiles()), Attribute) ; Attribute wieder herstellen
          EndIf
          If Not \IgnoreDate
            SetFileDate(\destinationDir + GetFilePart(\sourcefiles()), #PB_Date_Created, Date_Created)
            SetFileDate(\destinationDir + GetFilePart(\sourcefiles()), #PB_Date_Accessed, Date_Accessed)
            SetFileDate(\destinationDir + GetFilePart(\sourcefiles()), #PB_Date_Modified, Date_Modified)
          EndIf
          DeleteElement(\sourcefiles()) ; erfolgreich kopierte Dateien aus der Liste entfernen um Überprüfung zu ermöglich z.B. bei Abbruch
        EndIf
        If PeekI(\pStopVar) : Break : EndIf      
      EndWith
    Next
  EndIf
  
  If *CFE\Mutex
    UnlockMutex(*CFE\Mutex)
  EndIf
  
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    CompilerIf Defined(WM_CopyFileExFinished, #PB_Constant)
      If *CFE\hWnd
        PostMessage_(*CFE\hWnd, #WM_CopyFileExFinished, 0, 0)
      EndIf
    CompilerEndIf
  CompilerEndIf
  
EndProcedure
Hier das passende Beispiel:

Code: Alles auswählen

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
  Enumeration #WM_APP
    #WM_CopyFileExFinished
  EndEnumeration
CompilerEndIf

XIncludeFile "CopyFilesEx.pbi"

Global Stop.i

Procedure FileCallback(File.s, Dir.s, Sum.f, Procent.f)
  Static tmpFile.s
  Static tmpDir.s
  
  If tmpFile <> File And IsGadget(0)
    tmpFile = File
    SetGadgetText(0, "Copy File: " + File)
  EndIf
  If tmpDir <> Dir And IsGadget(1)
    tmpDir = Dir
    SetGadgetText(1, "To: " + Dir)
  EndIf
  If IsGadget(2)
    SetGadgetState(2, Int(Sum))
  EndIf
  If IsGadget(3)
    SetGadgetState(3, Int(Procent))
  EndIf
  If Stop
    ProcedureReturn #False
  EndIf
  ProcedureReturn #True
EndProcedure

Procedure OpenProgress()
  OpenWindow(0, 0, 0, 400, 160, "Progress CopyFilesEx", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  TextGadget(0, 10, 10, 380, 30, "")
  TextGadget(1, 10, 40, 380, 30, "")
  ProgressBarGadget(2, 10, 65, 380, 20, 0, 100)
  ProgressBarGadget(3, 10, 95, 380, 20, 0, 100)
  ButtonGadget(4, 150, 125, 80, 30, "cancel")
EndProcedure

Define.s SPath = PathRequester("Bitte Verzeichnis der zu kopierenden Dateien wählen:", "")
If Not SPath : End : EndIf
Define.s DPath = PathRequester("Bitte Zielordner wählen:", "")
If Not DPath : End : EndIf

Define.i Mutex = CreateMutex()
Define.CopyFilesEx CFE

With CFE
  \destinationDir = DPath
  \Mutex = Mutex
  \bufferSize = #PB_Default
  \callback = @FileCallback()
  \pStopVar = @Stop
  ;\IgnoreAttribute = #True
  ;\IgnoreDate = #True
  
  If ExamineDirectory(0, SPath, "")
    While NextDirectoryEntry(0)
      If DirectoryEntryType(0) = #PB_DirectoryEntry_File
        AddElement(\sourcefiles())
        \sourcefiles() = SPath + DirectoryEntryName(0)
      EndIf
    Wend
    FinishDirectory(0)
  EndIf
EndWith

OpenProgress()
CFE\hWnd = WindowID(0)

Define.i Thread = CreateThread(@CopyFilesEx(), @CFE)

Repeat
  Select WaitWindowEvent()
    CompilerIf #PB_Compiler_OS = #PB_OS_Windows
      Case #WM_CopyFileExFinished
        Break
    CompilerEndIf
    Case #PB_Event_CloseWindow
      Stop = #True
      WaitThread(Thread)
      Break
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 4
          Stop = #True
          WaitThread(Thread)
          Break
      EndSelect
  EndSelect
ForEver

If ListSize(CFE\sourcefiles())
  Debug Str(ListSize(CFE\sourcefiles())) + " Dateien nicht kopiert!"
  ForEach CFE\sourcefiles()
    Debug CFE\sourcefiles()
  Next
EndIf
Bei Problemen bitte hier melden!

Gruß

Thomas
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von NicTheQuick »

Ich bekomme in Zeile 41 der Include einen ungültigen Speicherzugriff gemeldet und sogar der Debugger schmiert ab:

Code: Alles auswählen

			WriteData(destID, *mem, buffersize)
Mehr kann ich deswegen leider nicht sagen.
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von ts-soft »

Danke für den Hinweis, werde ich am Wochenende mal nachgehen. Eigentlich wird kurz vorher alles
auf Gültigkeit geprüft, so das es schon sehr merkwürdig ist.

Gruß
Thomas

// edit
Frage noch: Threadsafe an oder aus?
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von NicTheQuick »

War natürlich aus. /:->

Hab's jetzt mal an geschaltet und siehe da, es stürzt nicht mehr ab. Dennoch passiert nichts:

Bild
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von ts-soft »

Dem Bild nach ist es Fertig, das Fenster mußt Du manuell schliessen, das geht nur unter Windows
automatisch. Wenn anschließend der Debugger keine Dateien mehr anzeigt, sollte alles kopiert sein!
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von NicTheQuick »

Stimmt, er hat alles kopiert. Hab nicht gemerkt, dass es unter Linux nicht automatisch beendet.

Hab dennoch etwas geändert und einen Fehler gefunden.
Ich hab den Code zum Verzeichnis-Einlesen etwas geändert:

Code: Alles auswählen

				If DirectoryEntryName(0) <> "." And DirectoryEntryName(0) <> ".."
					AddElement(\sourcefiles())
					\sourcefiles() = SPath + DirectoryEntryName(0)
				EndIf
Das Endbild:

Bild

Und der Fehler in der IDE

Bildlink, weil zu groß
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von ts-soft »

Die obige Änderung ist unnötig, da ich nur Dateien auswerte, . und .. sind wohl eher Ordner.

Das mit dem Bild muß ich noch untersuchen, vielleicht kommt INT(sum) über hundert und Linux
sieht das mal wieder etwas verbissener als Windows :mrgreen:

Gruß
Thomas
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
al90
Beiträge: 1101
Registriert: 06.01.2005 23:15
Kontaktdaten:

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von al90 »

Habs jetzt zwar nicht getestet, aber wie wäre es mit einer kleinen Änderung wie diese hier z.b.

Code: Alles auswählen

If GetUserDefaultLangID_() & $FF = #LANG_GERMAN
  ButtonGadget(4, 150, 125, 80, 30, "Abbrechen")
Else
  ButtonGadget(4, 150, 125, 80, 30, "Cancel")
EndIf
Dann wäre das Button zumindest auf Deutschen Systemen schonmal in Deutsch. Falls du das Wort noch in andere Sprachen kennst,
kannst du es ja auch noch erweitern. :wink:
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von ts-soft »

al90 hat geschrieben:Habs jetzt zwar nicht getestet, aber wie wäre es mit einer kleinen Änderung wie diese hier z.b.
Das Beispiel dient nur als Beispiel und sollte auch so seinen Zweck erfüllen :wink:
Der eigentliche Code enthält keine sprachspezifischen Meldungen.

Gruß
Thomas
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
al90
Beiträge: 1101
Registriert: 06.01.2005 23:15
Kontaktdaten:

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von al90 »

ts-soft hat geschrieben:Das Beispiel dient nur als Beispiel und sollte auch so seinen Zweck erfüllen :wink:
Der eigentliche Code enthält keine sprachspezifischen Meldungen.
War auch nur so ne idee, da der Aufwand nur geringfügig gewesen wäre. :wink:
Antworten