FileAppend data loss in a loop - autohotkey

In a loop, I use FileAppend to add text to a file. In every iteration of the loop, the data is accumulated in a variable and FileAppend is called every n iterations of the loop to save the data. Very occasionally, after a FileAppend call, the data accumulated in the variable during the next iteration is lost. As it is very intermittent, I could not reproduce this behavior. It seems like if, in some situation, the script would need a delay after FileAppend. Is this a known issue? I've search AHK forums and this site without report of such issue.
Here is a piece of code where this happens:
Loop, %intMax% ; for each record in the collection
{
if !Mod(A_Index, intProgressIterations)
; update the progress bar and save the data every intProgressIterations
; (when intProgressIterations / A_Index = 0)
{
ProgressUpdate(A_index, intMax, strProgressText)
; update progress bar only every %intProgressIterations% iterations
FileAppend, %strData%, %strFilePath%
strData := ""
; save the data accumulated in strData and empty it
}
strData := strData . BuildData(objCollection[A_Index])
; build the data for this record and add it to strData
}
More precisely, it is the content of one (or more) iteration of the line strData := strData . BuildData(objCollection[A_Index]) that is lost.

Could be any number of things. The file could be locked, there could be an error in your BuildData function that causes it not to produce data.
I'd recommend checking the last modified date before and after you append the data.
If it's the same, you can either try again and/or notify the user.
As to your question about the delay, it shouldn't need it, the script does not continue to the next line of code until it finishes writing to the file.

Following findings about file locking, this code with error management will be safer:
Loop, %intMax% ; for each record in the collection
{
if !Mod(A_Index, intProgressIterations)
; update the progress bar and save the data every intProgressIterations
; (when intProgressIterations / A_Index = 0)
{
ProgressUpdate(A_index, intMax, strProgressText)
; update progress bar only every %intProgressIterations% iterations
Loop
{
FileAppend, %strData%, %strFilePath%
; save the data accumulated in strData and empty it after the loop
if ErrorLevel
Sleep, 20
}
until !ErrorLevel or (A_Index > 50) ; after 1 second (20ms x 50), we have a problem
if (ErrorLevel)
strError := strError . "Error writing line " . A_Index . " Error: #" . A_LastError . "`n"
strData := ""
}
strData := strData . BuildData(objCollection[A_Index])
; build the data for this record and add it to strData
}

As an added point to this issue (for future searches)... I found that trying to FileAppend within a loop when the output file is on Dropbox, will also cause data loss.
Dropbox will detect updates to the file during the early cycles of the loop, and then lock it while it sends the updated (updating) file to the Dropbox servers. I've tracked data loss in a text file as short as 150 lines of text.
The solution I used was to create a temp output file outside of the Dropbox-monitored main folder, and write to it instead. When the loop is complete, copy the temp file back to Dropbox, and then delete the temp file.
Here's an partial code example of how I did the switch with a completed temp file, after the loop had finished:
; Copy the Daily Scrub temp file to var holding the filepath to todo.txt, then delete the Daily Scrub temp file
FileCopy, C:\temp-dailyscrub.txt, %todoFilePathParse%, 1
FileDelete, C:\temp-dailyscrub.txt

Related

Looking up data from a file, and storing them as variables

I'm new to autohotkey and I can't figure out how to solve this. Any help is appreciated.
I have list.txt which includes ids and names like this:
list.txt:
123124 - whatever
834019 - sometext
3980 - afjalkfj
I need a function that can do the following
lookup(id, name){
** The function here should lookup for the id inserted
then save ONLY the data related to it in variable x (not the full line)
}
Example
lookup(834019, x)
%x% = sometext
Please help me to do this. Thanks!
What you need in this case are
FileRead to read the file's contents into a variable.
A parsing loop to parse the text of each line.
The StrSplit() function to split the text of each line into an
array of Substrings using the specified Delimiters.
The second parameter (name) is redundant in this case. You can omit it:
x := lookup(834019)
MsgBox, % x
MsgBox, % lookup(3980)
lookup(id) {
FileRead, Contents, list.txt ; read the file's contents into the variable "Contents"
if not ErrorLevel ; Successfully loaded.
{
Loop, parse, Contents, `n, `r ; parse the text of each line
{
word_array1 := StrSplit(A_LoopField," - ").1 ; store the first substring into the variable "word_array1"
word_array1 := Trim(word_array1, " `t") ; trim spaces and tabs in this variable
If (word_array1 = id)
{
name := StrSplit(A_LoopField," - ").2
name := Trim(name, " `t")
return name
}
}
Contents := "" ; Free the memory.
}
else
MsgBox, A problem has been encountered while loading the File Contents
}

Setting a control name with concatenated text+variable for ControlSetText usage

TL;DR I created a new variable (destinationControl) by concatenating a string, a separate string variable, and then another string. I tried using the variable destinationControl with ControlSetText, but its not working. Can anyone tell me why?
Long Explanation:
I'm attempting to send some data from an excel spreadsheet into another application using AHK ControlSetText. My issue comes in when I need the script to detect which one of two possible programs is the active one (the detection part is working) and then based on the name of the program, set the destination control name is slightly different.
prog_A_segment := "abc"
prog_B_segment := "def"
;determine which program is open
IfInString, OpenProgram, ProgA
{
ctrlSegment := prog_A_segment
}
else
ctrlSegment := prog_B_segment
;set control variable
destinationControl := "WindowsForms10.EDIT.app.0." . ctrlSegment . "_r13_ad11"
;activate program
WinActivate, % OpenProgram
WinWaitActive, % OpenProgram,,3
;open vendor form
Sleep 300
Send ^o
Sleep 200
Send Vendors
sleep 200
Send {ENTER}
Sleep 2000
;This does not work:
;pass information to vendor form control
ControlSetText, %destinationControl%, %myNumber%, %OpenProgram%
I know I could just slightly more manually set them based on the open program but i have about 25 controls in total and the only difference is that center segment so I thought this would be a little more elegant and cleaner.
When I use the above method it doesn't appear AHK can find the control. I'm assuming it has something to do with how I combined a string and a variable. Is there some way to make this approach work without doing this instead:
IfInString, OpenProgram, ProgA
{
destinationControl1 := "WindowsForms10.EDIT.app.0.abc_r13_ad11"
....
destinationControl25 := "WindowsForms10.EDIT.app.0.abc_d52_ad11"
}
else
destinationControl1 := "WindowsForms10.EDIT.app.0.def_r13_ad11"
....
destinationControl25 := "WindowsForms10.EDIT.app.0.def_d52_ad11"
I agree with Josh Brobst that your first piece of code would work with the missing quote added.
Well, here's what you want to try anyways:
ctrlSegment := InStr(OpenProgram, ProgA) ? "abc" : "def"
Loop Parse, % "r13, ... ,d52", CSV
ControlSetText % "WindowsForms10.EDIT.app.0." ctrlSegment "_" A_LoopField "_ad11"
, % myNumber, % OpenProgram

Replace a middle string in .bat file using AutoHotKey without deleting file

I need to edit standalone.bat file using ahk script. I want to increase my heap size using ahk so below is line where i have to change heap in my bat file. Now i have trying to edit this using StringReplace and FileAppend but FileAppend keeps on appending string to the end
from
set "JAVA_OPTS=-Dprogram.name=%PROGNAME% -Xms64M -Xmx1426M %JAVA_OPTS%"
to
set "JAVA_OPTS=-Dprogram.name=%PROGNAME% -Xms64M -Xmx1426M %JAVA_OPTS%"xms000M
I am new to .ahk, i have tried this using some search
Loop, read, C:\standalone.bat
{
Line = %A_LoopReadLine%
replaceto = xms000M
IfInString, Line, Xmx1426M
, Line, replaceto, %Line%, %replaceto%
FileAppend, %replaceto%`n
StringReplace FileAppend
}
Is it possible to replace middle string using ahk. thanks
Fileappend will always append to the end of a file. Why do you want to prevent a temporary deletion of your batch file?
Typically, in ahk, you'd do it like this..
batFile = C:\standalone.bat
output := ""
Loop, read, %batFile%
{
Line = %A_LoopReadLine%
IfInString, Line, Xmx1426M
{
StringReplace, Line, Line, Xmx1426M, xms000M
; note: Regular Expressions can be used like Line := regExReplace(Line, "...", "...")
}
output .= Line . "`n" ; note: this is the same as if to say output = %output%%Line%`n or output := output . line "`n"
}
FileDelete, %batFile%
FileAppend, %output%, %batFile%
This will delete your file for some little milliseconds, just to recreate it with the new content afterwards. I don't really see any difference to editing it without deletion, because in either way, you'll need write access to the file.
Some words about your code sample:
IfInString, Line, Xmx1426M
, Line, replaceto, %Line%, %replaceto%
will be interpreted as
"If the string 'Line' contains 'Xmx1426M , Line, replaceto, %Line%, %replaceto%'"
which does not make any greater sense.
FileAppend, %replaceto%\n is lacking a destination file.
StringReplace FileAppend: these are two commands without any further parameters. You must never put two non-function-commands in the same line!

How to send line by line data from a .txt file using autohotkey?

Dear friends I am trying to send data from a .txt file to some another file by using Loop (read file contents) command in autohotkey. But it is not sending it line by line i.e. it is sending it continuously. As I made a script which is as follows-
F1::
Loop, read, C:\Data.txt
{
Loop, parse, A_LoopReadLine, %A_Tab%
{
Send %A_LoopField%
}
}
In the above example I made F1 a hotkey.
There is a data.txt file in my D: drive. Now I want that when I press F1 key it should send only one line at a time from data.txt file. When I again press F1 key, it should send next line from that file and so on. But it is not doing so. It is sending the data from data.txt file on the trot (continuously) till the end of the file.
Friends kindly suggest me any solution of this problem.
You can load the file-data outside the hotkey, then loop through it inside the hotkey and get the appropriate line. I'm not sure how fast this would be with huge files though.
; Uncomment this to load data from a file
;FileRead, fileData, C:\data.txt
; This is only for testing
fileData =
(LTrim
Line one
Line two
Line three
)
; Init lineIndex to 1
lineIndex := 1
F1::
Loop, Parse, fileData, `n
{
; A_Index holds the current loop-itteration
if (A_Index == lineIndex) {
SendInput, %A_LoopField%
; Increment lineIndex
lineIndex++
break
}
}
return
Esc::ExitApp

loop within loop - detecting end of loop in autohotkey

Let's say I have a bunch of filenames with the names of fruits in them. I want to auto-rename them based upon a folder full of reference files (dummy txt files which contain the names of fruits, a period, then the name of a dessert).
apple.tart, grape.jelly, kiwi.cake, mango.icecream, banana.pudding, cherry.cobbler, etc
I want to select all the files to be renamed, and drag them onto my script.
If a file in the loop already contains a certain combo, such as "cherry.cobbler", I simply want the dummyfile to be discarded, and the file should NOT be renamed "cherry.cobbler.cobbler"
If a file in the loop contains the word "kiwi", I want it to be changed so that it contains "kiwi.cake".
If a file in the loop contains a fruit not listed, I want a catchall string to be added. So "kumquat" would become "kumquat.nodessert"
It is condition #3 which is causing me trouble. I can't seem to come up with the right syntax to specify when the last dummyfile has been checked.
here's some pseudo code
Loop %0%
{
Path := %A_Index%
Loop %Path%, 1
LongPath = %A_LoopFileLongPath%
SplitPath LongPath, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
Loop thru folder of fruit combos
{
Stringsplit filenames from fruit-combos folder into %fruit% and %dessert%
If OutNameNoExt contains %fruit%.%dessert%
{
FileDelete fruit.combo dummyfile
continue; skip to next file to rename
)
If OutNameNoExt contains %fruit%
{
FileDelete fruit.combo dummyfile
StringReplace %fruit% with %fruit%.%dessert%
continue; skip to next file to rename
)
If OutNameNoExt not contains fruit.combo AND all dummy files have been checked
{
StringReplace %fruit% with %fruit%.nodessert
)
}
; proceed with next selected file
}
put condition 3 outside the inner loop and it seems to work