Skip to content

Commit be0ed6e

Browse files
committed
🐛 fix: The issue of waiting for export of too many sessions has been resolved.
1 parent 963bb16 commit be0ed6e

3 files changed

Lines changed: 232 additions & 29 deletions

File tree

internal/exporter/csv.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package exporter
22

33
import (
4+
"context"
45
"encoding/csv"
56
"fmt"
67
"os"
@@ -9,7 +10,23 @@ import (
910
"github.com/leoberbert/gocrt-decoder/internal/securecrt"
1011
)
1112

13+
type ExportProgress struct {
14+
Total int
15+
Written int
16+
}
17+
18+
type ExportProgressCallback func(ExportProgress)
19+
1220
func WriteSessionsCSV(path string, sessions []securecrt.Session) error {
21+
return WriteSessionsCSVWithProgress(context.Background(), path, sessions, nil)
22+
}
23+
24+
func WriteSessionsCSVWithProgress(
25+
ctx context.Context,
26+
path string,
27+
sessions []securecrt.Session,
28+
progress ExportProgressCallback,
29+
) error {
1330
f, err := os.Create(path)
1431
if err != nil {
1532
return fmt.Errorf("failed to create csv file: %w", err)
@@ -31,7 +48,15 @@ func WriteSessionsCSV(path string, sessions []securecrt.Session) error {
3148
return fmt.Errorf("failed to write csv header: %w", err)
3249
}
3350

34-
for _, s := range sessions {
51+
if progress != nil {
52+
progress(ExportProgress{Total: len(sessions), Written: 0})
53+
}
54+
55+
for i, s := range sessions {
56+
if err := ctx.Err(); err != nil {
57+
return err
58+
}
59+
3560
record := []string{
3661
s.Name,
3762
s.Hostname,
@@ -43,6 +68,9 @@ func WriteSessionsCSV(path string, sessions []securecrt.Session) error {
4368
if err := writer.Write(record); err != nil {
4469
return fmt.Errorf("failed to write csv record: %w", err)
4570
}
71+
if progress != nil && (i == len(sessions)-1 || (i+1)%25 == 0) {
72+
progress(ExportProgress{Total: len(sessions), Written: i + 1})
73+
}
4674
}
4775

4876
if err := writer.Error(); err != nil {

internal/securecrt/parser.go

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package securecrt
22

33
import (
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+
3052
func 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+
187277
func parseINI(path string) map[string]string {
188278
result := map[string]string{}
189279
file, err := os.Open(path)

0 commit comments

Comments
 (0)