Search code examples
design-patternsfreezensisnsdialogs

A way to warn the user about a little freeze by a function doing calculations in a NSIS installer


I'm doing my first NSIS script but i found a bump in the road. In fact i think is hard to explain only in the title, (maybe someone can help with that to) so let me explain fully:

I'm creating a installer that uses a few custom pages because i want the user select some options first (it uses nsDialogs), and depending of that do some tweaks in the with installation path (mostly autodetect it because it could depend to other things). All of this is working fine.

In some cases of that options, between checking if some files exists, it hash a file to look if the file is the one it expects (because it's going to patch it with a delta later). I used Crypto plugin or MD5 plugin, both are fine and both do what i want, but they hang the installer for a while (a second or so), i suppose because the file is a little big (it's about more than 100MB) and near that is the issue.

Normally in these cases, you select the option, goes to the next (custom) page, and in the creator function of the custom page autodetects the folder, and it directly do the file checks and when is checking the file hash, it hangs for a second and continues, but all this time hanged it only shows a blank page, because it didn't reach yet in the creator function the nsDialogs::Show instruction to show the window content. In that page you can change the folder, and if it's the case, once is changed it runs the checks again (it's a dedicated function that was called in both cases) and hangs again for a bit, but then the window shows everything and i can set a text to say something (in fact, it is what i did first), but with that automatic first time i can't do this.

That's the point: how to show something to the user to aware them about the installer is doing the hash calculations, instead showing only a blank window.

What I have tried or thought to do:

  • With nsDialogs, because it did the calculations first and didn't reach the nsDialogs::Show until later, i can't display anything in the window at that point (or, at least, is what i read in all the documentation i found about that). And, like the documentation says and it was tested, everything you put after the nsDialogs::Show instruction is executed when you push the next or back button.
  • Seeing that with nsDialogs at first not seems the way to go, i was searching if it's possible to show a window above the installation window (something like a MessageBox) and close it automatically, before and after the hash calculation respectively, showing only a text with a "Please wait" or kind of. But i didn't find a way to do it.
  • Maybe with a timer and do the checks a few miliseconds after could be done, but it seems to me a very cheap way to do it with some issues waiting to happen, mostly because depends of the machine speed, something i could do only in a last resort if it makes to show first the window with nsDialogs::Show, and later execute the check files with a timer. But, i want to do the checks as the folder is set because that function enables the "Next" button and i want that as soon as is possible, and adding timers to this doesn't look right.
  • Or is other more stylish way to do this but i didn't figure it out yet.

if it is not well understood the topic, i could add a little example tomorrow created from scratch to show this, because my main test is so big that is not point to paste all of that here.

Thanks!

EDIT:

This is the original example with the issue (Don't forget to add a path to a big file as marked):

Name "Example HASH Freeze"
Outfile "ExampleHASHFreeze.exe"

RequestExecutionLevel user
Unicode True
XPStyle on

!include nsDialogs.nsh
!include LogicLib.nsh

Page Custom FirstCreate
Page Custom SecondCreate
Page instfiles

var file
var hash
var info

Function FirstCreate

    StrCpy $file "" ; Add a path to a big file to do the hash. 150 MB or more.
    
    nsDialogs::Create 1018
    ${NSD_CreateLabel} 0u 64u 100% 12u "Hashing: $file"
    nsDialogs::Show
FunctionEnd

Function SecondCreate
    
    StrCpy $hash "hashing..."

    nsDialogs::Create 1018
    ${NSD_CreateLabel} 0u 58u 100% 12u "Hashing: $file"
    ${NSD_CreateLabel} 0u 70u 100% 12u "Hash: $hash"
    Pop $info

    call hashFile
    
    nsDialogs::Show
FunctionEnd

Function hashFile
    ${If} ${FileExists} "$file"
        md5dll::GetMD5File "$file"  ; Using MD5 Plugin
        ; Crypto::HashFile "MD5" "$file" ; Using Crypto Plugin
        Pop $0
        ${NSD_SetText} $info "Hash: $0" 
    ${Else}
        ${NSD_SetText} $info "Hash: FILE NOT FOUND" 
    ${EndIf}
FunctionEnd

Section
    MessageBox MB_OK "Hello world!"
SectionEnd

However, with Anders tips to use Banner plugin (that is what i was searching!), and BgWorker plugin with a nsDialogs Timer, nsDialogs render the window and at the same time it does the hash showing a banner, so now looks perfect! (Don't forget to add a path to a big file as marked).

Name "Example HASH Freeze Fix"
Outfile "ExampleHASHFreezeFix.exe"

RequestExecutionLevel user
Unicode True
XPStyle on

!include nsDialogs.nsh
!include LogicLib.nsh

Page Custom FirstCreate
Page Custom SecondCreate
Page instfiles

var file
var hash
var info

Function FirstCreate

    StrCpy $file "" ; Add a path to a big file to do the hash. 150 MB or more.
    
    nsDialogs::Create 1018
    ${NSD_CreateLabel} 0u 64u 100% 12u "Hashing: $file"
    nsDialogs::Show
FunctionEnd

Function SecondCreate
    
    StrCpy $hash "hashing..."

    nsDialogs::Create 1018
    ${NSD_CreateLabel} 0u 58u 100% 12u "Hashing: $file"
    ${NSD_CreateLabel} 0u 70u 100% 12u "Hash: $hash"
    Pop $info
    
    GetFunctionAddress $0 onShow_hack
    nsDialogs::CreateTimer $0 1
    
    nsDialogs::Show
FunctionEnd

Function hashFile
    ${If} ${FileExists} "$file"
        Banner::show "Calculating Hash..."
        md5dll::GetMD5File "$file"  ; Using MD5 Plugin
        ; Crypto::HashFile "MD5" "$file" ; Using Crypto Plugin
        Pop $0
        Banner::destroy
        ${NSD_SetText} $info "Hash: $0" 
    ${Else}
        ${NSD_SetText} $info "Hash: FILE NOT FOUND" 
    ${EndIf}
FunctionEnd

Function onShow_hack
    GetFunctionAddress $0 ${__FUNCTION__}
    nsDialogs::KillTimer $0
    GetFunctionAddress $0 hashFile
    BgWorker::CallAndWait
FunctionEnd

Section
    MessageBox MB_OK "Hello world!"
SectionEnd

Maybe it should disable the buttons meanwhile it's doing the calculations are not done but those things are easy. Thanks!


Solution

  • You are not really supposed to do heavy work on the custom pages. You can use the BgWorker plug-in to do background work. Combine that with a timer hack and you get this:

    !include nsDialogs.nsh
    
    Page Custom mypage
    
    Function hashfile
    Crypto::HashFile "MD5" "$somefilepath"
    Pop $0
    MessageBox "" $0
    FunctionEnd
    
    Function onShow_hack
    GetFunctionAddress $0 ${__FUNCTION__}
    nsDialogs::KillTimer $0
    GetFunctionAddress $0 hashfile
    BgWorker::CallAndWait
    FunctionEnd
    
    Function mypage
    nsDialogs::Create 1018
    Pop $0
    ${NSD_CreateButton} 0 13u 100% 12u "I do nothing"
    Pop $0
    GetFunctionAddress $0 onShow_hack
    nsDialogs::CreateTimer $0 1
    nsDialogs::Show
    FunctionEnd
    
    Section
    SectionEnd
    

    The Banner plug-in allows you to display a overlayed message...