Seite 2 von 2

Re: CopyFilesEx - Threaded und Crossplattform

Verfasst: 02.05.2010 17:12
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:

Re: CopyFilesEx - Threaded und Crossplattform

Verfasst: 04.03.2014 23:06
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

Re: CopyFilesEx - Threaded und Crossplattform

Verfasst: 04.03.2014 23:37
von GronkhLP
Danke, sehr cool :allright:

Re: CopyFilesEx - Threaded und Crossplattform

Verfasst: 05.03.2014 11:04
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.

Re: CopyFilesEx - Threaded und Crossplattform

Verfasst: 05.03.2014 12:58
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

Verfasst: 05.03.2014 13:10
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?

Re: CopyFilesEx - Threaded und Crossplattform

Verfasst: 05.03.2014 13:30
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.