Copy only modified files

Just starting out? Need help? Post your questions and find answers here.
imtcb
User
User
Posts: 12
Joined: Sun Jan 21, 2007 6:37 am

Copy only modified files

Post by imtcb »

Hi,

I have a batch file that I use to xcopy several directories using the /D flag, so that only modified files are copied when compared to the destination. It works OK, but I would like to be able to do more error handling and reporting so I found PureBasic. I have the demo, and have spent all day looking at sample code and tutorials, but I can't figure out how to copy a directory recursively and only overwrite those file which have been modified. I was going to go through a foreach loop and compare the modified dates of the source and destination, but that seems like WAY too much effort!

Can anyone help out?

Thanks in advance!
tcb
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

Code: Select all

RunProgram("xcopy", "from to /D")
:wink:

Yes it's a bit of work. It's a reason they included a program to do it.

Here is a procedure to list files recursively:

Code: Select all

Procedure ExamineDirectoryRecursive(Path.s, Pattern.s, List.s())
  Protected Dir = ExamineDirectory(#PB_Any, Path, Pattern)
  If Right(Path, 1) <> "\"
    Path + "\"
  EndIf
  If Dir
    While NextDirectoryEntry(Dir)
      If DirectoryEntryName(Dir) <> "." And DirectoryEntryName(Dir) <> ".."
        AddElement(List())
        List() = Path + DirectoryEntryName(Dir)
      EndIf
    Wend
    FinishDirectory(Dir)
  EndIf
  Dir = ExamineDirectory(#PB_Any, Path, "")
  If Dir
    While NextDirectoryEntry(Dir)
      If DirectoryEntryType(Dir) = #PB_DirectoryEntry_Directory
        If DirectoryEntryName(Dir) <> "." And DirectoryEntryName(Dir) <> ".."
          ExamineDirectoryRecursive(Path + DirectoryEntryName(Dir) + "\", Pattern, List())
        EndIf
      EndIf
    Wend
    FinishDirectory(Dir)
  EndIf
EndProcedure

NewList List.s()
ExamineDirectoryRecursive("c:\s3graphics\", "*.*", List())

ForEach List()
  Debug List()
Next
imtcb
User
User
Posts: 12
Joined: Sun Jan 21, 2007 6:37 am

Post by imtcb »

Thank you for the reply!

I guess I should use RunProgram, but I can't figure out how to capture the error levels in the event xcopy fails. I tried the OnError library, but that seems to be geared for PB errors only. Is there a way to capture the error level from xcopy?

Here is what I have so far.

Thanks again!
tcb

Code: Select all

Procedure init()
  Global FileName$, Date$, Time$, FileCreation$
  If ExamineDirectory(0, "C:\Batch", "LBM.*")
    NextDirectoryEntry(0)
    FileName$ = DirectoryEntryName(0)
    FileName$ = RemoveString(FileName$, "LBM.", 1)
    If ExamineDirectory(0, "C:\Batch", FileName$+"*.*")
      While NextDirectoryEntry(0)
        DFileName$ = DirectoryEntryName(0)
        DeleteFile("C:\Batch\"+DFileName$)
      Wend
    EndIf
  Else
    MessageRequester("Backup", "Error: No file exists!", 0)
    End
  EndIf
  Date$ = FormatDate("%mm/%dd/%yyyy", Date())
  Time$ = FormatDate("%hh:%ii", Date())
  FileCreation$ = FormatDate("%mm%dd%yy_%hh%ii", Date())
EndProcedure

Procedure RunXcopy(Source.s, Destination.s)
  RunProgram("xcopy", Source+" /D /E /S /C /Y", Destination, 1)
EndProcedure

init()
CreateDirectory("C:\c_sc")
RunXcopy("L:\sc", "C:\c_sc")
Result$ = "Pass"                                                      ; Would like to use error checking here
If CreateFile(0, "C:\Batch\"+FileName$+"."+FileCreation$+"."+Result$)
  WriteStringN(0, Date$+" "+Time$)
  CloseFile(0)
Else
  MessageRequester("Backup", "Error: Can't create file!", 0)
  End
EndIf
End
Godai
Enthusiast
Enthusiast
Posts: 171
Joined: Thu Oct 05, 2006 8:13 pm

Post by Godai »

You can read the process data using the Process library. (Check your help file :))
imtcb
User
User
Posts: 12
Joined: Sun Jan 21, 2007 6:37 am

Post by imtcb »

I looked at that and here is what I came up with:

Code: Select all

Procedure RunXcopy(Source.s, Destination.s)
  Global Source$, ExitCode
  Source$ = Source
  Xcopy = RunProgram("xcopy", Source+" /D /E /S /C /Y", Destination, #PB_Program_Open|#PB_Program_Read|#PB_Program_Error)
  If Xcopy
    While ProgramRunning(Xcopy)
      Debug ReadProgramString(Xcopy)
      Debug ReadProgramError(Xcopy)
      Debug AvailableProgramOutput(Xcopy)
      Debug ProgramFilename()
    Wend
    Debug ProgramExitCode(Xcopy)
    Debug IsProgram(Xcopy)
    Debug Source
    ExitCode.l = ProgramExitCode(Xcopy)
  Else
    ExitCode.l = 999
  EndIf
   
EndProcedure
I am using the ExitCode to determine success or failure, but I only get NULL or 0 as teh various outputs. I get output info from the example in the help file, but nothing I try with xcopy works. Not even changing the help example to /? xcopy gets me anything. Shouldn't the following give me a message box with the help text from xcopy?

Code: Select all

  Compiler = RunProgram("c:\windows\system32\xcopy.exe", "/?", "", #PB_Program_Open|#PB_Program_Read)
  Output$ = ""
  If Compiler  
    While ProgramRunning(Compiler)
      Output$ + ReadProgramString(Compiler) + Chr(13)
    Wend
    Output$ + Chr(13) + Chr(13)
    Output$ + "Exitcode: " + Str(ProgramExitCode(Compiler))     
  EndIf
  MessageRequester("Output", Output$)
Thanks again!
tcb
Clutch
User
User
Posts: 52
Joined: Sun Nov 26, 2006 6:11 am
Location: South Florida

Post by Clutch »

imtcb wrote:...
Shouldn't the following give me a message box with the help text from xcopy?
...
It should, but it looks like this may an issue with xcopy. I checked around on Google, and others have reported problems launching xcopy with commands like RunProgram() using other languages. Using API directly gives the same result. You may want to try creating and calling a batch file from within your program, redirecting xcopy's output to a file and reading that back in:

Code: Select all

If CreateFile(0, "C:\xcopy_.bat")
  WriteStringN(0, "C:\WINDOWS\system32\xcopy.exe /? >C:\xcopyResults.txt 2>C:\xcopyError.txt")
  CloseFile(0)
 
  RunProgram("cmd.exe", "/c C:\xcopy_.bat", "", #PB_Program_Wait)
 
  If ReadFile(0, "C:\xcopyResults.txt")
    Debug "xcopyResults:"
    Repeat
      Debug ReadString(0)
    Until Eof(0)
    CloseFile(0)
    Debug ""
  Else
    Debug "Unable to open/read xcopyResults.txt"
  EndIf
 
  If ReadFile(0, "C:\xcopyError.txt")
    Debug "xcopyError:"
    Repeat
      Debug ReadString(0)
    Until Eof(0)
    CloseFile(0)
    Debug ""
  Else
    Debug "Unable to open/read xcopyError.txt"
  EndIf
EndIf
Your best bet, though, would probably be to use only PureBasic to do the copying, using Trond's example above as a starting point.

HTH
-Clutch
"Ahead one third... ahead two thirds... Full ahead flank
And out from the belly of the whale came a prophet, Amen"
imtcb
User
User
Posts: 12
Joined: Sun Jan 21, 2007 6:37 am

Post by imtcb »

Thank you everyone who helped me (especially Trond). I finally went through the work to do it the right way instead of trying to rely on xcopy. I have it working remarkable well.

It creates missing directories
It copies any file not matching date modified
Optionally deletes files in the destination that are not in the source
It has error handling and reporting
It runs on a timer
It minimizes to the tasktray

I just need to get it to read settings from a pref file (working on it) and FTP/Email results and I'm done.

Thanks again!
tcb
User avatar
blueb
Addict
Addict
Posts: 1111
Joined: Sat Apr 26, 2003 2:15 pm
Location: Cuernavaca, Mexico

Post by blueb »

Xcopy.exe has been put on the back burner by Microsoft since XP came out.

Check out the many Shell functions, such as SHFileOperation that can help without reverting to MS-DOS.

E.G. A long time ago (when I was learning PB), I wrote a small PB program to move/copy files using SHFileOperation procedures.....

Code: Select all

;====================================================================== 
;   ShareCopy.PB -  A Muli-Function Copy Tool that uses: Shell32.dll 
;                   I found a subroutine on VB web-site - author unknown 
;                   modified for PureBasic -  Public Domain
;                   Originally written in 2002
;                   Bob Houle - updated Jan 23/07 PB 4.02 
;====================================================================== 
#Window1 = 1 
#W1Btn1 = 1 
#W1Btn2 = 2 
#W1Btn3 = 3 
#W1Btn4 = 4 
#W1Btn5 = 5 
#W1String1 = 6 
#W1String2 = 7 
#W1Check1 = 8 
#W1Check2 = 9 
#W1Check3 = 10 
#W1Check4 = 11 
#W1Check5 = 12 
#W1Text1 = 13 
#W1Text2 = 14 

#Window1Flags = #PB_Window_MinimizeGadget | #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_WindowCentered 
#Text1Flags = #PB_Text_Right 
#Text2Flags = #PB_Text_Right 

;====================================================================== 
;                [ Declares ] 
;====================================================================== 
Declare MyWindowCallback(WindowID, Message, wParam, lParam) 
Declare Button_Click(Index.l) 

;====================================================================== 
;                [ Globals ] 
;====================================================================== 
Global SHFileOp.SHFILEOPSTRUCT    ;Windows API Structure 

;======================================================================================== 

WinW=500 
WinH=230 ; Window sizes. 

hWnd.l = OpenWindow(#Window1,0,0,WinW,WinH,"Window-Like File Operations",#Window1Flags) 
 If CreateGadgetList(WindowID(1)) 
   ButtonGadget(#W1Btn1,7,200 ,89,25,"Copy") 
   ButtonGadget(#W1Btn2,105,200 ,89,25,"Move") 
   ButtonGadget(#W1Btn3,205,200 ,89,25,"Rename") 
   ButtonGadget(#W1Btn4,305,200 ,89,25,"Delete") 
   ButtonGadget(#W1Btn5,405,200 ,89,25,"Quit", 1) 
   StringGadget(#W1String1,220,8,250,21,"") 
   StringGadget(#W1String2,220,30 ,250,21,"") 
   CheckBoxGadget(#W1Check1,90,80 ,391,17,"Don't display a progress dialog box") 
   CheckBoxGadget(#W1Check2,90,100 ,403,17,"Respond with 'Yes to all' for any dialog box that is displayed") 
   CheckBoxGadget(#W1Check3,90,120 ,404,17,"Rename the file (eg:'Copy #1 of...') if the target name already exists") 
   CheckBoxGadget(#W1Check4,90,140 ,384,17,"Do not confirm the creation of a new directory if the operation requires it") 
   CheckBoxGadget(#W1Check5,90,160 ,398,17,"Perform the operation only on files if a wildcard filename (*.*) is specified") 
   TextGadget(#W1Text1,50,12 ,161,17,"Source File or Folder", #Text1Flags) 
   TextGadget(#W1Text2,50,35,161,17,"Destination File or Folder", #Text2Flags) 
 EndIf 
  
If hWnd 
;Message Loop 

; ----- Windows Messages 
  ; Callback is not required for this App, but included anyways :) 
  ; Might want to reply to Windows messages. 
  ; For now simply try re-sizing the window 
  SetWindowCallback(@MyWindowCallback()) 
  
; ----- PB specific messages 
 Repeat 
   EventID.l = WaitWindowEvent() 

  Select EventID 

          Case #PB_Event_Gadget 

              Select EventGadget() 
               Case #W1Btn1 ;----------Copy 
                    Button_Click(0) 
               Case #W1Btn2 ;----------Move 
                    Button_Click(1) 
               Case #W1Btn3 ;----------Rename 
                    Button_Click(2) 
               Case #W1Btn4 ;----------Delete 
                    Button_Click(3) 
               Case #W1Btn5 ;----------Quit 
                      EventID = #PB_Event_CloseWindow 
              EndSelect 

  EndSelect 

 Until EventID = #PB_Event_CloseWindow 

EndIf 
End ; program finish 

; ********************************************************************* 
;                [ Required Procedures ] 
; ********************************************************************* 


;====================================================================== 
;                [ Callback Procedure ] 
;====================================================================== 
Procedure MyWindowCallback(WindowID, Message, wParam, lParam) 
  Result = #PB_ProcessPureBasicEvents 
    Select message 
          Case #WM_SIZE    ;just testing 
              Beep_(50,50) 
    EndSelect 
  ProcedureReturn Result 
EndProcedure 

;====================================================================== 
;                [ SHFileOperation API Procedure ] 
;====================================================================== 
Procedure.l Button_Click(Index.l) 

;define variables 
 lFileOp.f 
 lresult.l 
 lFlags.w 

;Get status of checkboxes 
ChkDir.l = GetGadgetState(#W1Check4) 
ChkFilesOnly.l = GetGadgetState(#W1Check5) 
ChkRename.l = GetGadgetState(#W1Check3) 
ChkSilent.l = GetGadgetState(#W1Check1) 
ChkYesToAll.l = GetGadgetState(#W1Check2)  

;Get the edit box values 
FromDirectory.s = GetGadgetText(#W1String1) 
ToDirectory.s = GetGadgetText(#W1String2) 

;Find out which button was pressed 
 Select Index 
    Case 0 
        lFileOp = #FO_COPY 
    Case 1 
        lFileOp = #FO_MOVE 
    Case 2 
        lFileOp = #FO_RENAME 
    Case 3 
         ChkYesToAll = 0      ;No mattter what - confirm Deletes! Prevents OOPS! 
         lFileOp = #FO_DELETE 
 EndSelect 

If ChkSilent:lFlags = lFlags | #FOF_SILENT: EndIf 
If ChkYesToAll: lFlags = lFlags | #FOF_NOCONFIRMATION:EndIf 
If ChkRename: lFlags = lFlags | #FOF_RENAMEONCOLLISION: EndIf 
If ChkDir: lFlags = lFlags | #FOF_NOCONFIRMMKDIR: EndIf 
If ChkFilesOnly: lFlags = lFlags | #FOF_FILESONLY: EndIf 

; NOTE:  If you add the #FOF_ALLOWUNDO Flag you can move 
;        a file to the Recycle Bin instead of deleting it. 

  SHFileOp\wFunc = lFileOp 
  SHFileOp\pFrom = @FromDirectory 
  SHFileOp\pTo = @ToDirectory 
  SHFileOp\fFlags = lFlags 

 lresult = SHFileOperation_(SHFileOp) 

;  If User hit Cancel button While operation is in progress, 
;  the fAnyOperationsAborted parameter will be true 
;  - see win32api.inc For Structure details. 

If lresult <> 0 | SHFileOp\fAnyOperationsAborted:EndIf: ProcedureReturn 0 

 MessageRequester("Operation Has Completed", "PureBasic Rules!", 0) 
  ProcedureReturn = lresult 
EndProcedure 
; ================================================================
- It was too lonely at the top.

System : PB 6.21(x64) and Win 11 Pro (x64)
Hardware: AMD Ryzen 9 5900X w/64 gigs Ram, AMD RX 6950 XT Graphics w/16gigs Mem
imtcb
User
User
Posts: 12
Joined: Sun Jan 21, 2007 6:37 am

Post by imtcb »

Thanks again blueb!

It still needs a lot of work, but here is where I am now:

Code: Select all

Procedure ExamineDirectoryRecursive(Path.s, Pattern.s, List.s()) 
  Protected Dir = ExamineDirectory(#PB_Any, Path, Pattern) 
  If Right(Path, 1) <> "\" 
    Path + "\" 
  EndIf 
  If Dir 
    While NextDirectoryEntry(Dir) 
      If DirectoryEntryName(Dir) <> "." And DirectoryEntryName(Dir) <> ".."
        If DirectoryEntryType(Dir) = #PB_DirectoryEntry_Directory
          AddElement(List()) 
          List() = Path + DirectoryEntryName(Dir)  + "\"
        Else
          AddElement(List()) 
          List() = Path + DirectoryEntryName(Dir) 
        EndIf
      EndIf
    Wend 
    FinishDirectory(Dir) 
  EndIf 
  Dir = ExamineDirectory(#PB_Any, Path, "") 
  If Dir 
    While NextDirectoryEntry(Dir) 
      If DirectoryEntryType(Dir) = #PB_DirectoryEntry_Directory 
        If DirectoryEntryName(Dir) <> "." And DirectoryEntryName(Dir) <> ".." 
          ExamineDirectoryRecursive(Path + DirectoryEntryName(Dir) + "\", Pattern, List()) 
        EndIf 
      EndIf 
    Wend 
    FinishDirectory(Dir) 
  EndIf 
EndProcedure 

Procedure.s tcb_Copy(Source.s, Destination.s, Match) ;Match is a #True / #False to delete files in destination that are not in source
  Result$ = "pass"
  SourceDir$ = Source
  DestinationDir$ = Destination
  CreateDirectory(DestinationDir$)
  NewList SourceList.s() 
  ExamineDirectoryRecursive(SourceDir$, "*.*", SourceList()) 
  NewList DestinationList.s() 
  ExamineDirectoryRecursive(DestinationDir$, "*.*", DestinationList())
  If Match
    ResetList(DestinationList())
    While NextElement(DestinationList())
      Result = -1
      ForEach SourceList()
        If RemoveString(DestinationList(), DestinationDir$) = RemoveString(SourceList(), SourceDir$) : Result = 1 : Break : EndIf
      Next
      If Result = -1
        If Right(DestinationList(), 1) = "\"
          Result = DeleteDirectory(DestinationList(), "", #PB_FileSystem_Recursive|#PB_FileSystem_Force)
          If Result = 0 : Result$ = "fail" : EndIf
        Else
          Result = DeleteFile(DestinationList())
          If Result = 0 : Result$ = "fail" : EndIf
        EndIf
      EndIf
    Wend
  EndIf
  ResetList(SourceList())
  While NextElement(SourceList())
    Result = -1
    ForEach DestinationList()
      If RemoveString(SourceList(), SourceDir$) = RemoveString(DestinationList(), DestinationDir$)
        Result = 1
        If Right(SourceList(), 1) = "\" : Break : EndIf
        If GetFileDate(SourceList(), #PB_Date_Modified) = GetFileDate(DestinationList(), #PB_Date_Modified) : Break : EndIf
        Result = CopyFile(SourceList(), DestinationList())
        If Result = 0 : Result$ = "fail" : EndIf
        Break
      EndIf
    Next
    If Result = -1
      If Right(SourceList(), 1) = "\"
        Result = CreateDirectory(DestinationDir$+RemoveString(SourceList(), SourceDir$))
        If Result = 0 : Result$ = "fail" : EndIf
      Else
        Result = CopyFile(SourceList(), DestinationDir$+RemoveString(SourceList(), SourceDir$))
        If Result = 0 : Result$ = "fail" : EndIf
      EndIf
    EndIf
  Wend
  ProcedureReturn Result$
EndProcedure
Coolman
Enthusiast
Enthusiast
Posts: 103
Joined: Sat Sep 03, 2005 4:07 pm

Post by Coolman »

Test this freeware: xxcopy

http://www.xxcopy.com

example for cloner two repertories: xxcopy /FF /clone c:\rep d:\rep

attention of reading documentation before launching this order

I tested many programs of this kind, xxcopy is fastest

PS: afflicted for faults, translation in English carried out by software
collectordave
Addict
Addict
Posts: 1310
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: Copy only modified files

Post by collectordave »

You can write your own see this thread http://www.purebasic.fr/english/viewtop ... 01#p477101
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
Post Reply