@@ -2,6 +2,7 @@ package securecrt
22
33import (
44 "bufio"
5+ "context"
56 "errors"
67 "fmt"
78 "os"
@@ -27,7 +28,37 @@ type ParseResult struct {
2728 Warnings []string
2829}
2930
31+ type ParseProgress struct {
32+ Stage string
33+ CurrentPath string
34+ DirectoriesScanned int
35+ SessionsParsed int
36+ SessionsDecrypted int
37+ SessionsDecryptFailed int
38+ Warnings int
39+ }
40+
41+ type ParseProgressCallback func (ParseProgress )
42+
43+ type parseState struct {
44+ directoriesScanned int
45+ sessionsParsed int
46+ sessionsDecrypted int
47+ sessionsDecryptFailed int
48+ warnings int
49+ progress ParseProgressCallback
50+ }
51+
3052func ParseSessions (rootPath string , configPassphrase string ) (ParseResult , error ) {
53+ return ParseSessionsWithProgress (context .Background (), rootPath , configPassphrase , nil )
54+ }
55+
56+ func ParseSessionsWithProgress (
57+ ctx context.Context ,
58+ rootPath string ,
59+ configPassphrase string ,
60+ progress ParseProgressCallback ,
61+ ) (ParseResult , error ) {
3162 info , err := os .Stat (rootPath )
3263 if err != nil {
3364 return ParseResult {}, fmt .Errorf ("failed to stat root directory: %w" , err )
@@ -38,14 +69,30 @@ func ParseSessions(rootPath string, configPassphrase string) (ParseResult, error
3869
3970 result := ParseResult {}
4071 visited := map [string ]struct {}{}
41- if err := walkDirectory (rootPath , rootPath , "" , configPassphrase , visited , & result ); err != nil {
72+ state := & parseState {progress : progress }
73+ state .report ("Iniciando leitura das sessões..." , rootPath )
74+ if err := walkDirectory (ctx , rootPath , rootPath , "" , configPassphrase , visited , & result , state ); err != nil {
4275 return ParseResult {}, err
4376 }
77+ state .report ("Leitura finalizada." , rootPath )
4478
4579 return result , nil
4680}
4781
48- func walkDirectory (rootDir , dirPath , folderPath , configPassphrase string , visited map [string ]struct {}, result * ParseResult ) error {
82+ func walkDirectory (
83+ ctx context.Context ,
84+ rootDir ,
85+ dirPath ,
86+ folderPath ,
87+ configPassphrase string ,
88+ visited map [string ]struct {},
89+ result * ParseResult ,
90+ state * parseState ,
91+ ) error {
92+ if err := ctx .Err (); err != nil {
93+ return err
94+ }
95+
4996 resolved , err := filepath .EvalSymlinks (dirPath )
5097 if err != nil {
5198 resolved = dirPath
@@ -54,14 +101,16 @@ func walkDirectory(rootDir, dirPath, folderPath, configPassphrase string, visite
54101 return nil
55102 }
56103 visited [resolved ] = struct {}{}
104+ state .directoriesScanned ++
105+ state .report ("Lendo diretório..." , dirPath )
57106
58107 folderData := parseINI (filepath .Join (dirPath , "__FolderData__.ini" ))
59108 listedFolders := splitSecureCRTList (folderData ["Folder List" ])
60109 listedSessions := splitSecureCRTList (folderData ["Session List" ])
61110
62111 entries , err := os .ReadDir (dirPath )
63112 if err != nil {
64- result . Warnings = append (result . Warnings , fmt .Sprintf ("Could not read directory %s: %v" , dirPath , err ))
113+ addWarning (result , state , fmt .Sprintf ("Could not read directory %s: %v" , dirPath , err ), dirPath )
65114 return nil
66115 }
67116
@@ -85,12 +134,15 @@ func walkDirectory(rootDir, dirPath, folderPath, configPassphrase string, visite
85134
86135 orderedSessionFiles := make ([]string , 0 , len (iniFiles ))
87136 for _ , sessionStem := range listedSessions {
137+ if err := ctx .Err (); err != nil {
138+ return err
139+ }
88140 filePath , ok := iniFiles [sessionStem ]
89141 if ! ok {
90142 filePath = findCaseInsensitiveINI (dirPath , sessionStem )
91143 }
92144 if filePath == "" {
93- result . Warnings = append (result . Warnings , fmt .Sprintf ("Listed SecureCRT session '%s' was not found under %s." , sessionStem , dirPath ))
145+ addWarning (result , state , fmt .Sprintf ("Listed SecureCRT session '%s' was not found under %s." , sessionStem , dirPath ), dirPath )
94146 continue
95147 }
96148 orderedSessionFiles = append (orderedSessionFiles , filePath )
@@ -107,10 +159,13 @@ func walkDirectory(rootDir, dirPath, folderPath, configPassphrase string, visite
107159 orderedSessionFiles = append (orderedSessionFiles , remaining ... )
108160
109161 for _ , iniPath := range orderedSessionFiles {
162+ if err := ctx .Err (); err != nil {
163+ return err
164+ }
110165 sessionData := parseINI (iniPath )
111166 hostname := strings .TrimSpace (sessionData ["Hostname" ])
112167 if hostname == "" {
113- result . Warnings = append (result . Warnings , fmt .Sprintf ("Skipped '%s': missing Hostname field." , filepath .Base (iniPath )))
168+ addWarning (result , state , fmt .Sprintf ("Skipped '%s': missing Hostname field." , filepath .Base (iniPath )), iniPath )
114169 continue
115170 }
116171
@@ -125,7 +180,10 @@ func walkDirectory(rootDir, dirPath, folderPath, configPassphrase string, visite
125180 if passwordV2 != "" {
126181 decryptedPassword , err = DecryptPasswordV2 (passwordV2 , configPassphrase )
127182 if err != nil {
128- result .Warnings = append (result .Warnings , fmt .Sprintf ("Failed to decrypt password for '%s': %v" , sessionName , err ))
183+ addWarning (result , state , fmt .Sprintf ("Failed to decrypt password for '%s': %v" , sessionName , err ), iniPath )
184+ state .sessionsDecryptFailed ++
185+ } else {
186+ state .sessionsDecrypted ++
129187 }
130188 }
131189
@@ -144,17 +202,22 @@ func walkDirectory(rootDir, dirPath, folderPath, configPassphrase string, visite
144202 DecryptedPassword : decryptedPassword ,
145203 SourceFile : relFile ,
146204 })
205+ state .sessionsParsed ++
206+ state .report ("Processando sessões..." , iniPath )
147207 }
148208
149209 orderedChildDirs := make ([]string , 0 , len (childDirs ))
150210 processed := map [string ]struct {}{}
151211 for _ , folderName := range listedFolders {
212+ if err := ctx .Err (); err != nil {
213+ return err
214+ }
152215 if childPath , ok := childDirs [folderName ]; ok {
153216 orderedChildDirs = append (orderedChildDirs , childPath )
154217 processed [folderName ] = struct {}{}
155218 continue
156219 }
157- result . Warnings = append (result . Warnings , fmt .Sprintf ("Listed SecureCRT folder '%s' was not found under %s." , folderName , dirPath ))
220+ addWarning (result , state , fmt .Sprintf ("Listed SecureCRT folder '%s' was not found under %s." , folderName , dirPath ), dirPath )
158221 }
159222
160223 remainingChildNames := make ([]string , 0 , len (childDirs ))
@@ -171,19 +234,46 @@ func walkDirectory(rootDir, dirPath, folderPath, configPassphrase string, visite
171234 }
172235
173236 for _ , childDir := range orderedChildDirs {
237+ if err := ctx .Err (); err != nil {
238+ return err
239+ }
174240 childName := filepath .Base (childDir )
175241 childFolderPath := "/" + childName
176242 if folderPath != "" {
177243 childFolderPath = folderPath + "/" + childName
178244 }
179- if err := walkDirectory (rootDir , childDir , childFolderPath , configPassphrase , visited , result ); err != nil {
180- result .Warnings = append (result .Warnings , fmt .Sprintf ("Failed to process directory %s: %v" , childDir , err ))
245+ if err := walkDirectory (ctx , rootDir , childDir , childFolderPath , configPassphrase , visited , result , state ); err != nil {
246+ if errors .Is (err , context .Canceled ) {
247+ return err
248+ }
249+ addWarning (result , state , fmt .Sprintf ("Failed to process directory %s: %v" , childDir , err ), childDir )
181250 }
182251 }
183252
184253 return nil
185254}
186255
256+ func addWarning (result * ParseResult , state * parseState , warning , path string ) {
257+ result .Warnings = append (result .Warnings , warning )
258+ state .warnings ++
259+ state .report ("Aviso durante leitura..." , path )
260+ }
261+
262+ func (s * parseState ) report (stage , currentPath string ) {
263+ if s == nil || s .progress == nil {
264+ return
265+ }
266+ s .progress (ParseProgress {
267+ Stage : stage ,
268+ CurrentPath : currentPath ,
269+ DirectoriesScanned : s .directoriesScanned ,
270+ SessionsParsed : s .sessionsParsed ,
271+ SessionsDecrypted : s .sessionsDecrypted ,
272+ SessionsDecryptFailed : s .sessionsDecryptFailed ,
273+ Warnings : s .warnings ,
274+ })
275+ }
276+
187277func parseINI (path string ) map [string ]string {
188278 result := map [string ]string {}
189279 file , err := os .Open (path )
0 commit comments