CopyDirEx - Module Crossplattform

Share your advanced PureBasic knowledge/code with the community.
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

CopyDirEx - Module Crossplattform

Post by ts-soft »

This is like CopyDirectory, but with progress and cancel option.
Have a look at the included example to see how it works.

Code: Select all

  ;======================================================================
; Module:          CopyDirEx.pbi
;
; Author:          Thomas (ts-soft) Schulz
; Date:            Jan 31, 2016
; Version:         1.5
; Target Compiler: PureBasic 5.2+
; Target OS:       All
; License:         Free, unrestricted, no warranty whatsoever
;                  Use at your own risk
;======================================================================
; History:
; Version 1.5
; + small update for windows attributes (special windows 10)

; Version 1.4
; + small update to example

; Version 1.3
; + some optimization
; + more accurate progress

; Version 1.2
; + empty dirs missing, resolved
; + bug with attributes of dirs, resolved

DeclareModule CopyDirEx
 
  EnableExplicit
 
  Prototype.i CopyDirExCB(File.s, Dir.s, Sum.i, Procent.i)
 
  Declare CopyDirectoryEx(SourceDirectory.s,
                          DestinationDirectory.s,
                          Pattern.s = "",
                          BufferSize.l = 4096,
                          CustomEvent.l = #PB_Event_FirstCustomValue, ; This event is fired after Copying finished or canceld (SignalStop).
                          Callback.i = 0)                             ; See Prototype CopyDirExCB.
                                                                      ; Result = Thread (result from CreateThread()).
  Declare SignalStop()                                                ; send a signal to stop copying after actual file!
 
EndDeclareModule

Module CopyDirEx
 
  CompilerIf Not #PB_Compiler_Thread
    CompilerError "CopyDirEx requires ThreadSafe Compileroption!"
  CompilerEndIf
 
  Structure RecursiveFiles
    Directory.s
    Name.s
    Attributes.l
    Date.l[3]
    Size.q
    Type.b
  EndStructure
 
  Structure CopyThreadPara
    SourceDirectory.s
    DestinationDirectory.s
    BufferSize.l
    CustomEvent.l
    Callback.i
    cFiles.i
  EndStructure
 
  Global NewList RecursiveFiles.RecursiveFiles()
  Global Mutex = CreateMutex()
  Global Semaphore = CreateSemaphore()
  Global slash.s
 
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      slash = "\"
    CompilerDefault
      slash = "/"
  CompilerEndSelect
 
  ; private functions
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      #FILE_ATTRIBUTE_DEVICE              =     64 ;(0x40)
      #FILE_ATTRIBUTE_INTEGRITY_STREAM    =  32768 ;(0x8000)
      #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED  =   8192;(0x2000)
      #FILE_ATTRIBUTE_NO_SCRUB_DATA        = 131072;(0x20000)
      #FILE_ATTRIBUTE_VIRTUAL              =  65536;(0x10000)
      #FILE_ATTRIBUTE_DONTSETFLAGS = ~(#FILE_ATTRIBUTE_DIRECTORY|
                                       #FILE_ATTRIBUTE_SPARSE_FILE|
                                       #FILE_ATTRIBUTE_OFFLINE|
                                       #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED|
                                       #FILE_ATTRIBUTE_VIRTUAL|
                                       0)
      Macro SetFileAttributesEx(Name, Attribs)
        SetFileAttributes(Name, Attribs & #FILE_ATTRIBUTE_DONTSETFLAGS)
      EndMacro
    CompilerDefault
      Macro SetFileAttributesEx(Name, Attribs)
        SetFileAttributes(Name, Attribs)
      EndMacro
  CompilerEndSelect
 
  Procedure CreateDirectoryEx(DirectoryName.s, FileAttribute = #PB_Default)
    Protected i, c, tmp.s
   
    If Right(DirectoryName, 1) = slash
      DirectoryName = Left(DirectoryName, Len(DirectoryName) -1)
    EndIf
    c = CountString(DirectoryName, slash) + 1
    For i = 1 To c
      tmp + StringField(DirectoryName, i, slash)
      If FileSize(tmp) <> -2
        CreateDirectory(tmp)
      EndIf
      tmp + slash
    Next
    If FileAttribute <> #PB_Default
      SetFileAttributesEx(DirectoryName, FileAttribute)
    EndIf
    If FileSize(DirectoryName) = -2
      ProcedureReturn #True
    EndIf
  EndProcedure
 
  Procedure ExamineRecursiveDirectory(DirectoryName.s, Pattern.s, Directory.s = "")
    Protected Dir, Name.s, n
    Static cFiles.i
   
    If Directory = ""
      ClearList(RecursiveFiles())
      cFiles = 0
    EndIf
    If Right(DirectoryName,1) <> slash : DirectoryName + slash : EndIf
    Dir = ExamineDirectory(#PB_Any, DirectoryName, "")
    If Dir
      While NextDirectoryEntry(Dir)
        Name = DirectoryEntryName(Dir)
        If Name <> ".." And Name <> "."
          If DirectoryEntryType(Dir) = #PB_DirectoryEntry_Directory
            AddElement(RecursiveFiles())
            With RecursiveFiles()
              \Directory = Directory
              \Name = Name
              \Attributes = DirectoryEntryAttributes(Dir)
              For n = 0 To 2
                \Date[n] = DirectoryEntryDate(Dir, n)
              Next n
              \Size = DirectoryEntrySize(Dir)
              \Type = DirectoryEntryType(Dir)
            EndWith
            ExamineRecursiveDirectory(DirectoryName + Name, Pattern, Directory + Name + slash)
          EndIf
        EndIf
      Wend
      FinishDirectory(Dir)
    EndIf
    Dir = ExamineDirectory(#PB_Any, DirectoryName, Pattern)
    If Dir
      While NextDirectoryEntry(Dir)
        Name = DirectoryEntryName(Dir)
        If DirectoryEntryType(Dir) = #PB_DirectoryEntry_File
          AddElement(RecursiveFiles())
          cFiles + 1
          With RecursiveFiles()
            \Directory = Directory
            \Name = Name
            \Attributes = DirectoryEntryAttributes(Dir)
            For n = 0 To 2
              \Date[n] = DirectoryEntryDate(Dir, n)
            Next n
            \Size = DirectoryEntrySize(Dir)
            \Type = DirectoryEntryType(Dir)
          EndWith
        EndIf
      Wend
      FinishDirectory(Dir)
    EndIf
    If Directory = ""
      ResetList(RecursiveFiles())
    EndIf
    ProcedureReturn cFiles
  EndProcedure
 
  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
    EndIf
    If MemorySize(*mem) > 0
      FreeMemory(*mem)
    EndIf
    ProcedureReturn result
  EndProcedure
 
  Procedure CopyThread(*ctp.CopyThreadPara)
    Protected sourceID.i, destID.i, bufferSize.l = *ctp\BufferSize, position.q, Size.q, Procent.i, Sum.i, count.i = 0
    Protected CustomEvent.i = *ctp\CustomEvent, DestDir.s = *ctp\DestinationDirectory, SourceDir.s = *ctp\SourceDirectory
    Protected Callback.CopyDirExCB = *ctp\Callback, cFiles = *ctp\cFiles
   
    If Right(DestDir, 1) <> slash : DestDir + slash : EndIf
    If Right(SourceDir, 1) <> slash : SourceDir + slash : EndIf
   
    LockMutex(Mutex)
   
    If ListSize(RecursiveFiles())
      If CreateDirectoryEx(DestDir)
        With RecursiveFiles()
          ForEach RecursiveFiles()
            If TrySemaphore(Semaphore)
              UnlockMutex(Mutex)
              PostEvent(CustomEvent)
              Break
            EndIf
            If \Type = #PB_DirectoryEntry_Directory
              CreateDirectoryEx(DestDir + \Directory + \Name, \Attributes)
              Continue
            Else
              If FileSize(DestDir + \Directory) <> -2
                CreateDirectoryEx(DestDir + \Directory)
              EndIf
              sourceID = ReadFile(#PB_Any, SourceDir + \Directory + \Name)
              If IsFile(sourceID) = #False : count + 1 : Continue : EndIf ; lesen fehlgeschlagen, fortsetzen mit nächstem File.
              FileBuffersSize(sourceID, bufferSize)
              Size = Lof(sourceID)
              destID = CreateFile(#PB_Any, DestDir + \Directory + \Name)
              If IsFile(destID) = #False : CloseFile(sourceID) : count + 1 : Continue : EndIf ; erstellen fehlgeschlagen, fortsetzen mit nächstem File.
              FileBuffersSize(destID, bufferSize)
              Sum = Int((100 * count) / cFiles) + 1
              count + 1
            EndIf
            Repeat
              position = CopyFileBuffer(sourceID, destID, bufferSize)
              Procent = Int((100 * position) / Size) + 1
             
              If position = 0 : Procent = 100 : EndIf
             
              If Callback
                Callback(\Name, \Directory, Sum, Procent)
              EndIf
            Until position = 0
           
            SetFileAttributesEx(DestDir + \Directory + \Name, \Attributes)
            SetFileDate(DestDir + \Directory + \Name, 0, \Date[0])
            SetFileDate(DestDir + \Directory + \Name, 1, \Date[1])
            SetFileDate(DestDir + \Directory + \Name, 2, \Date[2])
           
          Next
          If Callback
            Callback("", "", 100, 100)
          EndIf
        EndWith
      EndIf
    EndIf
    UnlockMutex(Mutex)
    PostEvent(CustomEvent)
  EndProcedure
 
  ; public functions
  Procedure CopyDirectoryEx(SourceDirectory.s, DestinationDirectory.s, Pattern.s = "", BufferSize.l = 4096, CustomEvent.l = #PB_Event_FirstCustomValue, Callback.i = 0)
    Static CopyThreadPara.CopyThreadPara
    Protected Thread, cFiles
   
    If BufferSize = #PB_Default : BufferSize = 4096 : EndIf
    If BufferSize < 1024 : BufferSize = 1024 : EndIf
   
    LockMutex(Mutex)
    cFiles = ExamineRecursiveDirectory(SourceDirectory, Pattern)
    If Not ListSize(RecursiveFiles())
      Debug "ERROR: can't examine SourceDirectory!"
      UnlockMutex(Mutex)
      ProcedureReturn #False
    EndIf
   
    UnlockMutex(Mutex)
   
    With CopyThreadPara
      \SourceDirectory = SourceDirectory
      \DestinationDirectory = DestinationDirectory
      \BufferSize = BufferSize
      \CustomEvent = CustomEvent
      \Callback = Callback
      \cFiles = cFiles
    EndWith
   
    Thread = CreateThread(@CopyThread(), @CopyThreadPara)
    ProcedureReturn Thread
  EndProcedure
 
  Procedure SignalStop()
    SignalSemaphore(Semaphore)
  EndProcedure
EndModule

CompilerIf #PB_Compiler_IsMainFile
  ; example
 
  EnableExplicit
 
  Enumeration #PB_Event_FirstCustomValue
    #ProgressFinish
  EndEnumeration
 
  Global.s DestDir = GetTemporaryDirectory() + "purebasic"
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      DestDir + "\"
    CompilerDefault
      DestDir + "/"
  CompilerEndSelect
 
  Procedure Callback(File.s, Dir.s, Sum.i, Procent.i)
    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 = DestDir + Dir
      SetGadgetText(1, "To: " + DestDir + Dir)
    EndIf
    If IsGadget(2)
      SetGadgetState(2, Sum)
    EndIf
    If IsGadget(3)
      SetGadgetState(3, Procent)
    EndIf
  EndProcedure
 
  Procedure OpenProgress()
    OpenWindow(0, 0, 0, 500, 160, "Progress CopyDirEx", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    TextGadget(0, 10, 10, 480, 30, "")
    TextGadget(1, 10, 40, 480, 30, "")
    ProgressBarGadget(2, 10, 65, 480, 20, 0, 100)
    ProgressBarGadget(3, 10, 95, 480, 20, 0, 100)
    ButtonGadget(4, 150, 125, 160, 30, "cancel")
  EndProcedure
 
  OpenProgress()
 
  Define cancel = #False
  Define Thread = CopyDirEx::CopyDirectoryEx(#PB_Compiler_Home, DestDir, "", 4096, #ProgressFinish, @Callback())
 
  If IsThread(Thread)
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          CopyDirEx::SignalStop()
          HideWindow(0, #True)
          cancel = #True
         
        Case #PB_Event_Gadget
          If EventGadget() = 4 ; cancel
            CopyDirEx::SignalStop()
            cancel = #True
          EndIf
         
        Case #ProgressFinish
          If cancel
            MessageRequester("Progress CopyDirEx", "Copying canceled!")
          Else
            MessageRequester("Progress CopyDirEx", "Copying finished!")
          EndIf
          Break
      EndSelect
    ForEver
  EndIf
CompilerEndIf 
Greetings - Thomas
Last edited by ts-soft on Sun Jan 31, 2016 7:42 pm, edited 6 times in total.
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: CopyDirEx - Module Crossplattform

Post by RASHAD »

Nice one Thomas
Did not test it yet but it will be very useful for a lot of tasks (Setup software for exam)
Egypt my love
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: CopyDirEx - Module Crossplattform

Post by ts-soft »

You're welcome! :D
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
User avatar
Thunder93
Addict
Addict
Posts: 1788
Joined: Tue Mar 21, 2006 12:31 am
Location: Canada

Re: CopyDirEx - Module Crossplattform

Post by Thunder93 »

I'm always impressed with your contributes. You have so much talent. Thank you ts-soft.

Don't take this the wrong way.., not to be bossy or anything, but do keep up the superb work. :mrgreen:
ʽʽSuccess is almost totally dependent upon drive and persistence. The extra energy required to make another effort or try another approach is the secret of winning.ʾʾ --Dennis Waitley
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: CopyDirEx - Module Crossplattform

Post by ts-soft »

Update:
History wrote:; Version 1.2
; + empty dirs missing, resolved
; + bug with attributes of dirs, resolved
History wrote:; Version 1.3
; + some optimization
; + more accurate progress
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: CopyDirEx - Module Crossplattform

Post by Kwai chang caine »

GIANT !!! :shock:
I needed a code like this since a long time.
Because i have create my own backup software, but i'm not able to manage the multithreading :oops:
With you code, my program go becoming a jet :D
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: CopyDirEx - Module Crossplattform

Post by Fred »

Nice work here !
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: CopyDirEx - Module Crossplattform

Post by davido »

Thank you for sharing. :D

Excellent, instructive code.
DE AA EB
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: CopyDirEx - Module Crossplattform

Post by ts-soft »

Thanks to all :D

I have update the example (Version 1.4).

Greetings - Thomas
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: CopyDirEx - Module Crossplattform

Post by ts-soft »

Update:
History wrote:; Version 1.5
; + small update for windows attributes (special windows 10)
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
User avatar
Michael Vogel
Addict
Addict
Posts: 2797
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: CopyDirEx - Module Crossplattform

Post by Michael Vogel »

Nice, thanks...

Some tiny changes to reduce flickering:

Code: Select all

<< Add this procedure >>
Procedure SetGadgetTextSmooth(Gadget,text.s)
	If GetGadgetText(Gadget)<>text
		SetGadgetText(Gadget,text)
	EndIf
EndProcedure

<< Change two lines in procedure Callback >>
	SetGadgetTextSmooth(0,"Copy File: " + File)
	SetGadgetTextSmooth(1, "To: " + DestDir + Dir)

<< Change two lines in procedure OpenProgress >>
	TextGadget(0, 10, 10, 480, 25, "")
	TextGadget(1, 10, 40, 480, 25, "")
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: CopyDirEx - Module Crossplattform

Post by ChrisR »

Really great, thanks a lot for sharing :D

To reduce flickering a little more
For progress bars with large files, in addition to the SetGadgetText, post above

Code: Select all

<< Add this procedure >>
  Procedure SetProgressBarGadgetSmooth(Gadget,Value)
    If IsGadget(Gadget)
      If GetGadgetState(Gadget)<>Value
        SetGadgetState(Gadget,Value)
      EndIf
    EndIf
  EndProcedure

<< And replace >>
    If IsGadget(2)
      SetGadgetState(2, Sum)
    EndIf
<< By >>
    SetProgressBarGadgetSmooth(2, Sum)

Same for SetGadgetState(3, Procent) => SetProgressBarGadgetSmooth(3, Procent)
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: CopyDirEx - Module Crossplattform

Post by ChrisR »

I really like CopyDirEx with the progress bars based on the really written data :D
It is not the case when using CopyDirectory or other, especially on large files.

I allowed myself to make some additions:
. Added a progress bar on the total size
. Progress bar based on values rather than percentage (can be calulated then)
with the progress values on interface (eg: Total size: 42 MB/ 358 MB)
. Minor cosmetic in example

Correct me if needed, Still much thing to learn in programming, in PB.

Code: Select all

;======================================================================
; Module:          CopyDirEx.pbi
;
; Author:          Thomas (ts-soft) Schulz
; Date:            Jan 31, 2016
; Version:         1.5
; Target Compiler: PureBasic 5.2+
; Target OS:       All
; License:         Free, unrestricted, no warranty whatsoever
;                  Use at your own risk
;======================================================================
; History:
; Version 1.5.1
; + small update
; + Added a progress bar on total size + minor cosmetic to example
;
; Version 1.5
; + small update for windows attributes (special windows 10)

; Version 1.4
; + small update to example

; Version 1.3
; + some optimization
; + more accurate progress

; Version 1.2
; + empty dirs missing, resolved
; + bug with attributes of dirs, resolved

DeclareModule CopyDirEx

  EnableExplicit

  Prototype.i CopyDirExCB(File.s, Dir.s, Sum.i, Procent.i, Size.i)

  Declare CopyDirectoryEx(SourceDirectory.s,
                          DestinationDirectory.s,
                          Pattern.s = "",
                          BufferSize.l = 4096,
                          CustomEvent.l = #PB_Event_FirstCustomValue, ; This event is fired after Copying finished or canceled (SignalStop).
                          Callback.i = 0)                             ; See Prototype CopyDirExCB.
                                                                      ; Result = Thread (result from CreateThread()).
  Declare SignalStop()                                                ; send a signal to stop copying after actual file!

EndDeclareModule

Module CopyDirEx

  CompilerIf Not #PB_Compiler_Thread
    CompilerError "CopyDirEx requires ThreadSafe Compiler option!"
  CompilerEndIf

  Structure RecursiveFiles
    Directory.s
    Name.s
    Attributes.l
    Date.l[3]
    Size.q
    Type.b
  EndStructure

  Structure CopyThreadPara
    SourceDirectory.s
    DestinationDirectory.s
    BufferSize.l
    CustomEvent.l
    Callback.i
    cFiles.i
    tSize.i
  EndStructure

  Global NewList RecursiveFiles.RecursiveFiles()
  Global Mutex = CreateMutex()
  Global Semaphore = CreateSemaphore()
  Global slash.s

  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      slash = "\"
    CompilerDefault
      slash = "/"
  CompilerEndSelect

  ; private functions
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      #FILE_ATTRIBUTE_DEVICE              =     64 ;(0x40)
      #FILE_ATTRIBUTE_INTEGRITY_STREAM    =  32768 ;(0x8000)
      #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED  =   8192;(0x2000)
      #FILE_ATTRIBUTE_NO_SCRUB_DATA        = 131072;(0x20000)
      #FILE_ATTRIBUTE_VIRTUAL              =  65536;(0x10000)
      #FILE_ATTRIBUTE_DONTSETFLAGS = ~(#FILE_ATTRIBUTE_DIRECTORY|
                                       #FILE_ATTRIBUTE_SPARSE_FILE|
                                       #FILE_ATTRIBUTE_OFFLINE|
                                       #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED|
                                       #FILE_ATTRIBUTE_VIRTUAL|
                                       0)
      Macro SetFileAttributesEx(Name, Attribs)
        SetFileAttributes(Name, Attribs & #FILE_ATTRIBUTE_DONTSETFLAGS)
      EndMacro
    CompilerDefault
      Macro SetFileAttributesEx(Name, Attribs)
        SetFileAttributes(Name, Attribs)
      EndMacro
  CompilerEndSelect

  Procedure CreateDirectoryEx(DirectoryName.s, FileAttribute = #PB_Default)
    Protected i, c, tmp.s

    If Right(DirectoryName, 1) = slash
      DirectoryName = Left(DirectoryName, Len(DirectoryName) -1)
    EndIf
    c = CountString(DirectoryName, slash) + 1
    For i = 1 To c
      tmp + StringField(DirectoryName, i, slash)
      If FileSize(tmp) <> -2
        CreateDirectory(tmp)
      EndIf
      tmp + slash
    Next
    If FileAttribute <> #PB_Default
      SetFileAttributesEx(DirectoryName, FileAttribute)
    EndIf
    If FileSize(DirectoryName) = -2
      ProcedureReturn #True
    EndIf
  EndProcedure

  Procedure.s ExamineRecursiveDirectory(DirectoryName.s, Pattern.s, Directory.s = "")
    Protected Dir, Name.s, n
    Static cFiles.i, tSize.i

    If Directory = ""
      ClearList(RecursiveFiles())
      cFiles = 0
      tSize = 0
    EndIf
    If Right(DirectoryName,1) <> slash : DirectoryName + slash : EndIf
    Dir = ExamineDirectory(#PB_Any, DirectoryName, "")
    If Dir
      While NextDirectoryEntry(Dir)
        Name = DirectoryEntryName(Dir)
        If Name <> ".." And Name <> "."
          If DirectoryEntryType(Dir) = #PB_DirectoryEntry_Directory
            AddElement(RecursiveFiles())
            With RecursiveFiles()
              \Directory = Directory
              \Name = Name
              \Attributes = DirectoryEntryAttributes(Dir)
              For n = 0 To 2
                \Date[n] = DirectoryEntryDate(Dir, n)
              Next n
              \Size = DirectoryEntrySize(Dir)
              \Type = DirectoryEntryType(Dir)
            EndWith
            ExamineRecursiveDirectory(DirectoryName + Name, Pattern, Directory + Name + slash)
          EndIf
        EndIf
      Wend
      FinishDirectory(Dir)
    EndIf
    Dir = ExamineDirectory(#PB_Any, DirectoryName, Pattern)
    If Dir
      While NextDirectoryEntry(Dir)
        Name = DirectoryEntryName(Dir)
        If DirectoryEntryType(Dir) = #PB_DirectoryEntry_File
          AddElement(RecursiveFiles())
          cFiles + 1
          tSize + DirectoryEntrySize(Dir)
          With RecursiveFiles()
            \Directory = Directory
            \Name = Name
            \Attributes = DirectoryEntryAttributes(Dir)
            For n = 0 To 2
              \Date[n] = DirectoryEntryDate(Dir, n)
            Next n
            \Size = DirectoryEntrySize(Dir)
            \Type = DirectoryEntryType(Dir)
          EndWith
        EndIf
      Wend
      FinishDirectory(Dir)
    EndIf
    If Directory = ""
      ResetList(RecursiveFiles())
    EndIf
    ProcedureReturn StrU(cFiles) + "|" + StrU(tSize)
  EndProcedure



  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
    EndIf
    If MemorySize(*mem) > 0
      FreeMemory(*mem)
    EndIf
    ProcedureReturn result
  EndProcedure

  Procedure CopyThread(*ctp.CopyThreadPara)
    Protected sourceID.i, destID.i, bufferSize.l = *ctp\BufferSize, position.q, Size.q, Procent.i, Sum.i, count.i = 0
    Protected CustomEvent.i = *ctp\CustomEvent, DestDir.s = *ctp\DestinationDirectory, SourceDir.s = *ctp\SourceDirectory
    Protected Callback.CopyDirExCB = *ctp\Callback, cFiles = *ctp\cFiles, tSize = *ctp\tSize
    Protected tDSize.i = 0
    If Right(DestDir, 1) <> slash : DestDir + slash : EndIf
    If Right(SourceDir, 1) <> slash : SourceDir + slash : EndIf

    LockMutex(Mutex)

    If ListSize(RecursiveFiles())
      If CreateDirectoryEx(DestDir)
        With RecursiveFiles()
          ForEach RecursiveFiles()
            If TrySemaphore(Semaphore)
              UnlockMutex(Mutex)
              PostEvent(CustomEvent)
              Break
            EndIf
            If \Type = #PB_DirectoryEntry_Directory
              CreateDirectoryEx(DestDir + \Directory + \Name, \Attributes)
              Continue
            Else
              If FileSize(DestDir + \Directory) <> -2
                CreateDirectoryEx(DestDir + \Directory)
              EndIf
              sourceID = ReadFile(#PB_Any, SourceDir + \Directory + \Name)
              If IsFile(sourceID) = #False : count + 1 : tDSize + Size : Continue : EndIf ; Read failed, continue with next file.
              FileBuffersSize(sourceID, bufferSize)
              Size = Lof(sourceID)
              destID = CreateFile(#PB_Any, DestDir + \Directory + \Name)
              If IsFile(destID) = #False : CloseFile(sourceID) : count + 1 : tDSize + Size : Continue : EndIf ; Create failed, continue with next file.
              FileBuffersSize(destID, bufferSize)
              count + 1
            EndIf
            If IsGadget(31) : SetGadgetAttribute(31, #PB_ProgressBar_Maximum, Size) : EndIf
            Procent = 0
            Repeat
              position = CopyFileBuffer(sourceID, destID, bufferSize)

              If position > 0   ;File Size + Total DestDir size corrected. Real size on disk <> FileSize() linked to fixed clusters size
                tDSize - Procent + position  ;Total DestDir Size - Previous Disk File Size + Write pointer Position
                Procent = position
              Else
                tDSize - Procent + Size  ;Total DestDir Size - Previous Disk File Size + File Size
                Procent = Size
              EndIf

              If Callback
                Callback(\Name, \Directory, count, Procent, tDSize)
              EndIf
            Until position = 0
            ;Debug DestDir + \Directory + \Name + ": Size " + Str(Size) + " / Size on Disk :" + Str(tFSize)  + " - Total Size " + Str(tSize) + " / Total Size on Disk :" + Str(tDSize)

            SetFileAttributesEx(DestDir + \Directory + \Name, \Attributes)
            SetFileDate(DestDir + \Directory + \Name, 0, \Date[0])
            SetFileDate(DestDir + \Directory + \Name, 1, \Date[1])
            SetFileDate(DestDir + \Directory + \Name, 2, \Date[2])

          Next
          ;Debug "Total Size " + Str(tSize) + " / Total Size on Disk :" + Str(tDSize)

          If Callback
            If IsGadget(31) : SetGadgetAttribute(31, #PB_ProgressBar_Maximum, 100) : EndIf
            Callback("", "", cFiles, 100, tSize)
          EndIf
        EndWith
      EndIf
    EndIf
    UnlockMutex(Mutex)
    PostEvent(CustomEvent)
  EndProcedure

  ; public functions
  Procedure CopyDirectoryEx(SourceDirectory.s, DestinationDirectory.s, Pattern.s = "", BufferSize.l = 4096, CustomEvent.l = #PB_Event_FirstCustomValue, Callback.i = 0)
    Static CopyThreadPara.CopyThreadPara
    Protected Thread, StCountSize.s, cFiles, tSize

    If BufferSize = #PB_Default : BufferSize = 4096 : EndIf
    If BufferSize < 1024 : BufferSize = 1024 : EndIf

    LockMutex(Mutex)
    StCountSize = ExamineRecursiveDirectory(SourceDirectory, Pattern)
    If Not ListSize(RecursiveFiles())
      Debug "ERROR: can't examine SourceDirectory!"
      UnlockMutex(Mutex)
      ProcedureReturn #False
    EndIf

    UnlockMutex(Mutex)
    cFiles = Val(StringField(StCountSize,1,"|"))
    tSize = Val(StringField(StCountSize,2,"|"))

    If IsGadget(11) : SetGadgetAttribute(11, #PB_ProgressBar_Maximum, tSize) : EndIf
    If IsGadget(21) : SetGadgetAttribute(21, #PB_ProgressBar_Maximum, cFiles) : EndIf

    With CopyThreadPara
      \SourceDirectory = SourceDirectory
      \DestinationDirectory = DestinationDirectory
      \BufferSize = BufferSize
      \CustomEvent = CustomEvent
      \Callback = Callback
      \cFiles = cFiles
      \tSize = tSize
    EndWith

    Thread = CreateThread(@CopyThread(), @CopyThreadPara)
    ProcedureReturn Thread
  EndProcedure

  Procedure SignalStop()
    SignalSemaphore(Semaphore)
  EndProcedure
EndModule

CompilerIf #PB_Compiler_IsMainFile
  ; example

  EnableExplicit

  Enumeration #PB_Event_FirstCustomValue
    #ProgressFinish
  EndEnumeration

  Define.s SourceDir
  Global.s DestDir

  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      DestDir + "\"
    CompilerDefault
      DestDir + "/"
  CompilerEndSelect

  Procedure.s SizeIt(Value.q)
    Protected unit.b=0, byte.q, pos.l, nSize.s
    byte = Value
    While byte >= 1024
      byte / 1024 : unit + 1
    Wend
    If unit : nSize = StrD(Value/Pow(1024, unit), 15) : pos.l = FindString(nSize, ".") : Else : nSize = Str(Value) : EndIf
    If unit : If pos <  4 : nSize=Mid(nSize,1,pos+2) : Else : nSize=Mid(nSize, 1, pos-1) : EndIf : EndIf
    ProcedureReturn nSize+" "+StringField("b ,KB,MB,GB,TB,PB", unit+1, ",")
  EndProcedure

  Procedure Callback(File.s, Dir.s, Sum.i, Procent.i, Size.i)
    Static tmpFile.s
    Static tmpDir.s
    Static tmpSum.i, tmpProcent.i, tmpSize.i
    Protected Text.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: " + DestDir + Dir)
    EndIf
    If tmpSize <> Size And IsGadget(11)
      tmpSize = Size
      SetGadgetState(11, Size)
      If IsGadget(10)
        Text = "Total Size ("+RSet(SizeIt(Size), 8)+"\"+RSet(SizeIt(GetGadgetAttribute(11, #PB_ProgressBar_Maximum)), 8)+")"
        If GetGadgetText(10) <> text : SetGadgetText(10,text) : EndIf
      EndIf
    EndIf
    If tmpSum <> Sum And IsGadget(21)
      tmpSum = Sum
      SetGadgetState(21, Sum)
      If IsGadget(20) : SetGadgetText(20, "Nb Files ("+StrU(Sum)+"\"+StrU(GetGadgetAttribute(21, #PB_ProgressBar_Maximum))+")") : EndIf
    EndIf
    If tmpProcent <> Procent And IsGadget(31)
      tmpProcent = Procent
      SetGadgetState(31, Procent)
      If IsGadget(30)
        Text = "File Size ("+RSet(SizeIt(Procent), 8)+"\"+RSet(SizeIt(GetGadgetAttribute(31, #PB_ProgressBar_Maximum)), 8)+")"
        If GetGadgetText(30) <> text : SetGadgetText(30,text) : EndIf
      EndIf
    EndIf
  EndProcedure

  Procedure OpenProgress()
    OpenWindow(0, 0, 0, 500, 180, "Progress CopyDirEx", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    TextGadget(0, 10, 10, 480, 15, "")
    ;SetGadgetFont(0, FontID(8))
    TextGadget(1, 10, 30, 480, 15, "")
    ;SetGadgetFont(1, FontID(8))
    TextGadget(10, 10, 50, 240, 15, "")
    ;SetGadgetFont(10, FontID(8))
    ProgressBarGadget(11, 10, 65, 480, 10, 0, 100)
    TextGadget(20, 10, 80, 240, 15, "")
    ;SetGadgetFont(20, FontID(8))
    ProgressBarGadget(21, 10, 95, 480, 10, 0, 100)
    TextGadget(30, 10, 110, 240, 15, "")
    ;SetGadgetFont(30, FontID(8))
    ProgressBarGadget(31, 10, 125, 480, 10, 0, 100)
    ButtonGadget(5, 170, 145, 150, 30, "Cancel")
  EndProcedure

  ;LoadFont(8,"Consolas",8)   ; Add a proportional font in the counter texts to decrease blinking

  OpenProgress()

  SourceDir = PathRequester("Select SOURCE Folder", #PB_Compiler_Home)
  DestDir = PathRequester("Select DESTINATION Folder. THE COPY WILL START IMMEDIATELY", "")
  If SourceDir = "" Or DestDir = "" Or SourceDir = DestDir : End : EndIf

  Define Rtn, Cancel = #False
  Define Thread = CopyDirEx::CopyDirectoryEx(SourceDir, DestDir, "", 4096, #ProgressFinish, @Callback())

  If IsThread(Thread)
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          Rtn = MessageRequester("Copy in progress","Copy in progress, are you sure you want to Cancel and Quit?",#PB_MessageRequester_YesNoCancel)
          If Rtn = #PB_MessageRequester_Yes
            CopyDirEx::SignalStop()
            HideWindow(0, #True)
            Cancel = #True
          EndIf

        Case #PB_Event_Gadget
          If EventGadget() = 5 ; Cancel
            Rtn = MessageRequester("Copy in progress","Copy in progress, are you sure you want to Cancel and Quit?",#PB_MessageRequester_YesNoCancel)
            If Rtn = #PB_MessageRequester_Yes
              CopyDirEx::SignalStop()
              Cancel = #True
            EndIf
          EndIf

        Case #ProgressFinish
          If Cancel
            MessageRequester("Progress CopyDirEx", "Copying Canceled!")
          Else
            MessageRequester("Progress CopyDirEx", "Copying Finished!")
          EndIf
          Break
      EndSelect
    ForEver
  EndIf
CompilerEndIf
Little John
Addict
Addict
Posts: 4779
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: CopyDirEx - Module Crossplattform

Post by Little John »

This is very useful. Image
Many thanks, Thomas!

I'd like to suggest a tiny change in the Callback procedure of the demo code, in order to reduce flickering:

Code: Select all

      If tmpDir <> Dir And IsGadget(1)
         ; tmpDir = DestDir + Dir       ; old
         tmpDir = Dir                   ; new: less flickering
         SetGadgetText(1, "To: " + DestDir + Dir)
      EndIf
miskox
Enthusiast
Enthusiast
Posts: 107
Joined: Sun Aug 27, 2017 7:37 pm
Location: Slovenia

Re: CopyDirEx - Module Crossplattform

Post by miskox »

Hello!

I just copied/pasted this source (viewtopic.php?f=12&t=58657#p500373) to the PB (demo version) and it cannot compile: it gives this error

Code: Select all

Line 93: constant not found: #FILE_ATTRIBUTE_DIRECTORY
Please help.

Thank you.
Saso
Post Reply