Archive: CopyFiles with detail handling


CopyFiles with detail handling
Hello,

I just started using NSIS about two days ago. I'm trying to create an installer (pretty much empty) that copies files from a dvd disc to the destination computer. CopyFiles works for the copying part, but it doesn't work for the detail screen during installation. My question is pretty simple:

[1]How do I copy all the files (161) and folders (38), [2]make it print every copied item (file or folder) and [3]update the status bar while copying?

[1] I'm using CopyFiles /SILENT "$EXEDIR\installer_files\*.*" "$INSTDIR" 2312474. All files are located in a dir (called installer_files), which is located in the installer.exe dir. The integer is the complete size of the files.

[2] I'm using RecFind.nsh for this, but it seems to be searching backwards (Z to A)...

[3] RecFind seems to do some updating, but not the way I want it: I'd like to see the bar reach the end when the last file is copied.

I read this topic, but I had trouble following the conversation: http://forums.winamp.com/showthread....ight=copyfiles

Any help is welcome, thanks in advance.

edit: This is what I have so far:

  ${RecFindOpen} "$EXEDIR\installer_files" $R0 $R1
DetailPrint "Dir: $R0"
${RecFindFirst}
${GetSize} "$EXEDIR\installer_files\$R0" "/M=$R1 /S=0K /G=0" $0 $1 $2
DetailPrint "File: $R0\$R1 ($0 KB)"
; CopyFiles /SILENT "$EXEDIR\installer_files\$R0\$R1" "$INSTDIR"
${RecFindNext}
${RecFindClose}

I'm using RecFind.nsh for this, but it seems to be searching backwards (Z to A)...
Not sure, but my theory is that Find and FindNext probably find things in the order they are stored on the disk and not necessarily in alphabetical order. (If you just copy a bunch of files from Explorer, you'll notice then that the order is not always alphabetical.) I'm not sure what to suggestion other than get a list first, sort the list, and then start copying.

Another alternative you might consider is to use the Locate header included with NSIS. You can seach for files/folders and use a callback function to do the copy. In addtion, you can combine this with the NxS banner plugin to allow you to display a progress bar during the copy progress. (The first link above has examples of how you can do this.)

Thank you for your reaction, Comperio.

Right now, my first section looks like the example given at E.1.2, (Example (Locate with banner - NxS plugin required)) including the function LocateCallBack. Before the copying I recreate the dir structure (Example (Recreate directory structure)). At installation it pops up a window with progress, but it just hangs at about 2/3 of the way (it just jumps there it doesn't move anymore...). Is it necessary for the window to pop up?

Situation now:
Directory structure is recreated.
Window (nxs) pops up when I copy the files.
Status bar jumps to 2/3 and just waits there.
All files are copied to $INSTDIR in stead of $INSTDIR\original_dir.
The instfiles page of the installer doesn't show any copy info (DetailPrint should do this, right? But it doesn't)


Is it necessary for the window to pop up?
Yes, this is how the nxs plugin works. To update your window, you'll have to call nxs::Update in your callback function. (You may have to determine how many files there are ahead of time so that you can set the maximum position number (/max) to the maximum number of files instead of the default of 100. Then, you'd just count the files as you copy them and set the position (/pos) to the current count of the file you are copying. (full nxs documentation is here)

And yes, you are correct about DetailPrint--it should update the progress bar. Just make sure you place those commands in the callback function you use for Locate.

And you don't have to use the banner (nxs). The reason for doing so is that the progress bar is based on the total number of commands in all sections. Because you are looping and becuase you probably have to do more than just copy files, the progress bar itself probably won't indicate the correct "progress" when copying files.

(Another thought: You might check out Afrow's RealProgress plugin too.)

edit:
You can recreate the directory structure on the fly by checking the value of $R8 in the locate callback function. Just call CreateDirectory '$R8' for each file.

I copied the example from E.1.2 and it indeed says to jump to 78 of the way in the Update function. I've set the max to 161, but how can I add 1 (to /pos) each time it updates?

About the DetailPrint, that the list it creates when performing an action, right? So it adds a line to the Details list. But in my case it doesn't show anything, it just says

Output folder: D:\dir
Created uninstaller: D:\dir\uninst.exe
Completed
So it skips all the file copying and dir creating stuff...

Isn't it possble to use only the RealProgress plugin? I can use ${Locate} to create folders, copy files, update the RealProgress plugin and add lines to the Detail screen, right?

If I use $R8 to create a dir, it tries to recreate the original dir ($R8 holds F:\original\dir and I just need the 'dir' part)

how can I add 1 (to /pos) each time it updates?
You'd have to create your own counter.
Example, let's say you set a variable called COUNTER as your file counter. Then, in the callback function, you'd do something like this:

Function Callback
IntOp $Counter $Counter +1
nxs::Update /NOUNLOAD /sub "copying $R7" /pos $Counter
; *** rest of your code here ***
FunctionEnd


About the DetailPrint, that the list it creates when performing an action, right? So it adds a line to the Details list. But in my case it doesn't show anything...
It should work. But, DetailPrint only works from within a section. Are you using LOCATE in a section? Also, the DetailPrint command needs to go in your callback function. (I assume you are doing this, but I thought I'd check.)

Isn't it possble to use only the RealProgress plugin?
Yes, you can certainly do this. However, it looks like the position is based on a number from 1-100 (corresponding to the percent you want the bar to be). So, you may have to add some additional calculations. Using my previous example for the counter, you may need another variable for the percentage.
example (assuming the variable PERCENT is used for calculating the percentage of the bar):

Function Callback
IntOp $Counter $Counter +1
IntOp $Percent $Counter / 161
IntOp $Percent $Percent * 100
RealProgress::SetProgress /NOUNLOAD $Percent
; *** rest of your code here ***
FunctionEnd

... DetailPrint only works from within a section. Are you using LOCATE in a section? Also, the DetailPrint command needs to go in your callback function.
I have a ${Locate} in my first section, which calls LocateCallBack. LocateCallBack contains my DetailPrint "Copied $R7". If I were to use DetailPrint in my section, it would only be called once... But I want it to be called for every file.

Thanks for the mathmatical stuff (should've read the entire manual before asking this... :) ).

So here's where I am now:
;-----------------------------------Sections
Section "!Install" inst1
SectionIn RO
SetOutPath "$INSTDIR"

StrCpy $R0 "$EXEDIR\installer_files" ;Directory structure from
StrCpy $R1 "$INSTDIR" ;Directory structure into
StrLen $R2 $R0
${Locate} "$R0" "/L=D" "CreateDirStruct"

StrCpy $Counter 0
StrCpy $Percent 0

${Locate} "$EXEDIR\installer_files" "/L=FD /M=*.* /B=1" "LocateCallback"
DetailPrint "Copying done."

;Call writeRegs
WriteUninstaller "$INSTDIR\uninst.exe"
SectionEnd

;-----------------------------------Functions
Function CreateDirStruct
StrCpy $1 $R9 '' $R2
CreateDirectory '$R1$1'
Push $0
FunctionEnd

Function LocateCallback
StrCmp $R0 $R8 abortcheck
StrCpy $R0 $R8

IntOp $Counter $Counter + 1
IntOp $Percent $Counter / 161
IntOp $Percent $Percent * 100
;MessageBox MB_OK "Counter=$Counter$\nPercent=$Percent"
RealProgress::SetProgress /NOUNLOAD $Percent

abortcheck:
Pop $0
StrCmp $0 1 0 +2
StrCpy $0 StopLocate

StrCmp $R9 '' end

CopyFiles /SILENT "$EXEDIR\installer_files" "$INSTDIR"
DetailPrint "Copied $R7"

end:
Push $0
FunctionEnd
For some reason it calls LocateCallBack, jumps straight on to abortcheck and stop locating... Without any copying being done...

You probably don't need that first LOCATE call--you can do it all on one step using the 2nd locate call. And since you are using the RealProgress plugin, you probably don't need the /B=1 (for the banner).

I also discovered that you have to call SetDetailsPrint to "both" in order to get the details to change. (Not sure if this is a function of NSIS or ${Locate}--never got that far.)

It also occured to me that you may want to check for errors during the copy to let the user know there was something wrong. I introduced another variable called "ErrorFlag" that gets set to -1 if there were errors (0 otheriwse).

Take a look at the attached code--it should be easy to follow.


Thansk a lot for taking so much time to help me! Really appreciate it.

I replaced my code with yours. The DetailPrint works now and the installer also copies all files. Only it doesn't create all dirs, just the ones that are about 5 levels deep.

Section "!Install" inst1
SectionIn RO
SetOutPath "$INSTDIR"

StrCpy $R0 "$EXEDIR\installer_files" ;Directory structure from
StrCpy $R1 "$INSTDIR" ;Directory structure into
StrLen $R2 $R0

StrCpy $Counter 0
StrCpy $Percent 0
StrCpy $ErrorFlag 0

${Locate} "$EXEDIR\installer_files" "/L=FD /M=*.*" "LocateCallback"
; Check for errors
StrCmp $ErrorFlag -1 Errors Continue
Errors:
MessageBox MB_YESNO "During installation, errors occured. Continue installation?" IDYES Continue
Abort ; and delete files


Continue:
DetailPrint "Copying done."
;Call writeRegs
WriteUninstaller "$INSTDIR\uninst.exe"
SectionEnd

Function LocateCallback
; Make sure details print
SetDetailsPrint both

; Set progress bar
IntOp $Counter $Counter + 1
IntOp $Percent $Counter / 161
IntOp $Percent $Percent * 100
; MessageBox MB_OK "Counter=$Counter$\nPercent=$Percent"
RealProgress::SetProgress /NOUNLOAD $Percent

; Create the directory
; ($R6 is blank if file)
StrCmp $R6 "" 0 copyfiles
DetailPrint "Creating directory $R7..."
CreateDirectory "$R9"
IfErrors FolderError end

copyfiles:
CopyFiles /SILENT "$EXEDIR\installer_files" "$INSTDIR"
IfErrors FileError 0
DetailPrint "Copied $R7"
goto end

FolderError:
DetailPrint "ERROR CREATING FOLDER $R7!"
goto end

FileError:
DetailPrint "ERROR COPYING FILE $R7!"

end:
StrCpy $ErrorFlag -1
StrCmp $R9 "" 0 +2 ; no more files
StrCpy $0 StopLocate
Push $0
FunctionEnd
The weirdness lies in the CopyFiles line. When I use CopyFiles /SILENT "$EXEDIR\installer_files" "$INSTDIR" to copy the files, it copies just the installer_files directory including its contents. And when I use CopyFiles /SILENT "$R9" "$INSTDIR" it copies as mentioned above...

Also the progress bar doesn't advance any sooner than 100%. $Percent stays 0 until it reaches 161 files.

Sorry, I was so caught up in helping with the progress bar, I didn't look at the rest of the code!

I think I see the problem with the CopyFiles code, but I'll have to think about a good solution.

The issue here is that the CopyFiles command should be copying from "$R9" to $INSTDIR\[path your files]. But here's the issue: Due to the way the locate plugin deals with the stack, you usually can't call any functions in the callback fucntion that might manuiplate the stack or you can run into problems. I'll give it some thought and will post it once I've figured it out.

As far as why the progress bar fails to update, I'm not sure. I'll look into this too. (perhaps you can PM AfrowUK to see if he's got any ideas since the RealProgress plugin is his baby.)


Ok, try the attached function. You'll also need to add this to the top of your script:


!include WordFunc.nsh
!insertmacro WordFind

Thank you very much for finding a possible sollution, but this morning I found my own. :) Snippet from LocateCallBack:


StrCpy $R0 "$EXEDIR\installer_files" ; Directory structure from
; StrCpy $R1 "$INSTDIR" ; Directory structure into
StrLen $R2 $R0 ; offset for destination diretory

StrCpy $1 $R9 '' $R2
; Create the directory
; ($R6 is blank if file)
StrCmp $R6 "" 0 copyfiles
DetailPrint "Creating directory $R7..."
CreateDirectory "$INSTDIR$1"
IfErrors FolderError end

copyfiles:
CopyFiles /SILENT "$R0$1" "$INSTDIR$1"
IfErrors FileError 0
DetailPrint "Copied $R7"
goto end
I used a lot of MessageBox' to find it, but here it is. It's probably the worst possible sollution, but it works: It creates all dirs (I used a bit of code from the Recreate example) and copies the files correct.
I want to thank you very much for helping me with this issue! :)

The status bar still doesn't progress correctly (btw: Including the dirs to be created, there are 199 items which increment the $Counter). So I decided to make it move every two items:
  ; Set progress bar
IntOp $Counter $Counter + 1
IntOp $Percent $Counter / 2 ; 199 items
; IntOp $Percent $Percent * 100
RealProgress::SetProgress /NOUNLOAD $Percent
This works. It's not really correct, since some files are over 200MB and others are just 4b.

So, thank you very, very much for all your help and time. Couldn't have done it without you!! ;)

Now, there are some other issues with my installer and I was wondering if you could help me with this too or should I make another topic for these:
[1]I found it pretty hard to place a brandingimage for the installer. I actually had to create a custom page with a creator function to place the image. Now I want to do the same thing for the uninstaller, but I can't get it to work.
[2]The brandingimage is read from a dir called installer, but I want it inside the installer so that dir can be deleted an I only need the installer.

Usually, it's best to put new items under new threads. (It just makes it easier for others to search and find the topics later.) But since you asked, I'll take a stab:

1) Create custom pages in the uninstall the same way as an install. Except use UninstPage instead of Page and prefix your uninstall function page functions with "un." to let the compiler know to add these to the uninstall. (Refer to the help manuals and examples for more info.)

2) You can place your images in the PLUGSINDIR to ensure they get delted when you are done with them. (You'll proabably want to edit the page's INI file on the fly to add the full path of the image into the proper field. Use WriteIniStr and the text would be "$PLUGINSDIR\imagefilename.bmp.)


Thanks again, works perfectly (except for the uninstaller).

So the code I now have:

Function .onInit
InitPluginsDir
File /oname=$PLUGINSDIR\logo.bmp installer\logo.bmp
FunctionEnd


Code I'm using for the installer (It's located before the license page):
Page custom brandimage "" ""

Function brandimage
SetBrandingImage "$PLUGINSDIR\logo.bmp"
FunctionEnd
Code for the uninstaller I have (This one is before uninstConfirm):
UninstPage custom un.brandimage "" ""

Function un.brandimage
SetBrandingImage "$PLUGINSDIR\logo.bmp"
FunctionEnd
But the brandingimage for the uninstaller won't work. The line AddBrandingImage left 90 2 works for both uninstaller and installer, correct?

Hmm, it seems that .onInit only works for the installer. For the uninstaller I have to use un.onInit. So my .onInit and un.onInit now contain the same lines and it works!

Well, I guess my application is complete than... :D I'd like to thank you, Comperio, very very much for all your help and time spent on helping me! You're the best! :up:


:o

Glad I could be of help. :D