My installer needs to change one line in a rather large (500Kb, about 11k lines) json file. I'm using AdvReplaceInFile to find and replace the occurrence.
Problem is, it takes about 3~ seconds to finish the job. I can kill the application using the same file, but if it's restarted automatically within that time window, even though the replace is successful, the process will overwrite the file again later on.
I'm assuming the algorithm responsible for search & replace is what's causing the slowdown.
How can I make it go faster other than writing my own DLL plugin to do so?
NSIS code was never designed to be fast and any call to a Int* function involves at least 2 string to number conversions etc.
Using a slightly slower version of the function you can prevent other processes from opening the file for writing:
Function AdvReplaceInFile
Exch $0 ;file to replace in
Exch
Exch $1 ;number to replace after
Exch
Exch 2
Exch $2 ;replace and onwards
Exch 2
Exch 3
Exch $3 ;replace with
Exch 3
Exch 4
Exch $4 ;to replace
Exch 4
Push $5 ;minus count
Push $6 ;universal
Push $7 ;end string
Push $8 ;left string
Push $9 ;right string
Push $R0 ;file1
Push $R1 ;file2
Push $R2 ;read
Push $R3 ;universal
Push $R4 ;count (onwards)
Push $R5 ;count (after)
Push $R6 ;temp file name
GetTempFileName $R6
FileOpen $R1 $0 a ;file to search in
FileOpen $R0 $R6 a ;temp file
StrLen $R3 $4
StrCpy $R4 -1
StrCpy $R5 -1
loop_read:
ClearErrors
FileRead $R1 $R2 ;read line
IfErrors exit
StrCpy $5 0
StrCpy $7 $R2
loop_filter:
IntOp $5 $5 - 1
StrCpy $6 $7 $R3 $5 ;search
StrCmp $6 "" file_write1
StrCmp $6 $4 0 loop_filter
StrCpy $8 $7 $5 ;left part
IntOp $6 $5 + $R3
IntCmp $6 0 is0 not0
is0:
StrCpy $9 ""
Goto done
not0:
StrCpy $9 $7 "" $6 ;right part
done:
StrCpy $7 $8$3$9 ;re-join
IntOp $R4 $R4 + 1
StrCmp $2 all loop_filter
StrCmp $R4 $2 0 file_write2
IntOp $R4 $R4 - 1
IntOp $R5 $R5 + 1
StrCmp $1 all loop_filter
StrCmp $R5 $1 0 file_write1
IntOp $R5 $R5 - 1
Goto file_write2
file_write1:
FileWrite $R0 $7 ;write modified line
Goto loop_read
file_write2:
FileWrite $R0 $R2 ;write unmodified line
Goto loop_read
exit:
FileSeek $R1 0 SET
!if "${NSIS_PTR_SIZE}" > 4
System::Call 'kernel32::SetEndOfFile(p$R1)'
!else
System::Call 'kernel32::SetEndOfFile(i$R1)'
!endif
FileSeek $R0 0 SET
ClearErrors
mov_loop:
FileRead $R0 $R2
IfErrors mov_done
FileWrite $R1 $R2
Goto mov_loop
mov_done:
FileClose $R0
FileClose $R1
SetDetailsPrint none
Delete $R6
SetDetailsPrint lastused
Pop $R6
Pop $R5
Pop $R4
Pop $R3
Pop $R2
Pop $R1
Pop $R0
Pop $9
Pop $8
Pop $7
Pop $6
Pop $5
;These values are stored in the stack in the reverse order they were pushed
Pop $0
Pop $1
Pop $2
Pop $3
Pop $4
FunctionEnd
The only way to actually make it faster would be to call a external program or write a plugin...