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

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von ts-soft »

al90 hat geschrieben:War auch nur so ne idee, da der Aufwand nur geringfügig gewesen wäre. :wink:
Okay, dann ergänze den Code bitte um die Equivalente in Linux und MacOS, dann werde ich es sofort einbauen :mrgreen:
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
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 »

Update:
Angepaßt an PB5.2x und höher. Jetzt als Modul und Crossplattform ohne Einschränkungen!

Code: Alles auswählen

; CopyFilesEx
; Version 2.0
; Author: Thomas (ts-soft) Schulz
; erstellt: 27.04.2010
; zuletzt geändert: 04.03.2014
; PB-Version: 5.20 und höher
; Crossplattform

DeclareModule CopyFilesEx
  
  CompilerIf Not #PB_Compiler_Thread
    CompilerError "CopyFilesEx requires ThreadSafe Compileroption!"
  CompilerEndIf
  
  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
    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
    FinishEvent.i
    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
  
  Declare CopyFilesEx(*CFE.CopyFilesEx)
EndDeclareModule

Module CopyFilesEx
  ; 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
    
    If *CFE\FinishEvent = 0
      *CFE\FinishEvent = #PB_Event_FirstCustomValue
    EndIf
    
    PostEvent(*CFE\FinishEvent)
    
  EndProcedure
EndModule

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
  
  UseModule CopyFilesEx
  
  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
  
  Enumeration #PB_Event_FirstCustomValue
    #FinishEvent
  EndEnumeration
  
  Define.s SPath = PathRequester("Bitte Verzeichnis der zu kopierenden Dateien wählen:", "")
  If SPath = "" : End : EndIf
  Define.s DPath = PathRequester("Bitte Zielordner wählen:", "")
  If DPath = "" : End : EndIf
  
  Define.i Mutex = CreateMutex()
  Define.CopyFilesEx CFE
  
  With CFE
    \destinationDir = DPath
    \Mutex = Mutex
    \bufferSize = #PB_Default
    \callback = @FileCallback()
    \FinishEvent = #FinishEvent
    \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()
  
  Define.i Thread = CreateThread(@CopyFilesEx(), @CFE)
  
  Repeat
    Select WaitWindowEvent()
        
      Case #FinishEvent
        Break
      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   
CompilerEndIf
Der Code dient dem Kopieren von Dateien (nicht Verzeichnissen) mit Fortschritt!
Wer Verzeichnisse Kopieren möchte, sollte die Dateien der Verzeichnisse nacheinander
kopieren. Alles andere macht auch nicht so viel Sinn, da der Zugriff durch mehrere
Threads auf dieselbe Festplatte eher Performanceeinbussen als Gewinn bringt!

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
GronkhLP
Beiträge: 72
Registriert: 14.11.2013 22:43
Wohnort: Köln
Kontaktdaten:

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von GronkhLP »

Danke, sehr cool :allright:
Bild
Benutzeravatar
_JON_
Beiträge: 389
Registriert: 30.03.2010 15:24

Re: CopyFilesEx - Threaded und Crossplattform

Beitrag von _JON_ »

Danke, gutes Module.
ts-soft hat geschrieben:Update:
Threads auf dieselbe Festplatte eher Performanceeinbussen als Gewinn bringt!
Nicht unbedingt, robocopy hat eine multithread option http://technet.microsoft.com/en-us/maga ... 42631.aspx
und diese beschleunigt das kopieren ein wenig.
PureBasic 5.46 LTS (Windows x86/x64) | windows 10 x64 Oktober failure
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 »

Auch Robocopy kann physikalische Gegenheiten nicht umgehen. Wenn mehrere Threads gleichzeitig auf dieselbe Platte
zugreifen, gibt es viele unnötige Bewegungen des Schreibkopfes, die mehr Zeit kosten, als das Schreiben der Dateien
nacheinander.

Vorteile nur wenn mehr Platten im Spiel sind, bzw. in einigen RAID-Verbünden. Also vernachlässigbar im Homebereich.
Robocopy ist ja auch eher für Server gedacht, wo die Gegenheiten doch anders sind, also mehr Platten, RAID usw.

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
CodeCommander
Beiträge: 213
Registriert: 02.03.2014 16:06

Beitrag von CodeCommander »

Wie sieht es mit SDD Platten aus? Da gibt es kein Schreibkopf und Schreibarm.Da ist die Zugriffszeit immer gleich egal wo. Kann man bei SSD gleichzeitig mehrere Sektoren lesen und beschreiben? Gibt es Performance-Vorteile wenn man mehrere Kopierungen in Threads auslagert?
Zuletzt geändert von CodeCommander am 18.01.2015 14:21, insgesamt 1-mal geändert.
~ DELETE ~
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 »

@denjenigen, der meinen Titel als Benutzernamen mißbraucht:

Es kann IMHO nur einer zur Zeit auf eine Platte schreiben, somit gibt es auch bei SSD keine Vorteile,
wenn mehrere Threads auf dieselbe Platte zuzugreifen. Vorteile gibt es erst, wenn mehrere Platten
im Spiel sind.

Also, die Nachteile gegenüber normalen Festplatten fallen zwar weg, aber Vorteile ergeben sich keine.
Es wird trotzdem nicht unbedingt schneller.
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
Antworten