Skip to content

Commit dfba14d

Browse files
authored
Improve error handling
- Improved the error handling when using `catalogue` and `download` commands. - Reduced the timeout for the login in headless mode. - Fixed a few errors in the documentation and added an example for running Gogg in debug mode in Windows.
1 parent a8ffe79 commit dfba14d

File tree

7 files changed

+89
-38
lines changed

7 files changed

+89
-38
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ Additionally, it allows users to perform the following actions:
6565
- Download game files (like installers, patches, and bonus content)
6666
- Filter files to be downloaded by platform, language, and other attributes like content type
6767
- Download files using multiple threads to speed up the process
68-
- Resume interrupted downloads
68+
- Resume interrupted downloads and only download missing or newer files
69+
- Verify the integrity of downloaded files by calculating their hashes
6970

7071
## Getting Started
7172

client/login.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,19 @@ func Login(loginURL string, username string, password string, headless bool) err
3737
ctx, cancel := createChromeContext(headless)
3838
defer cancel()
3939

40+
log.Info().Msg("Trying to login to GOG.com.")
41+
4042
// Perform the login
41-
finalURL, err := performLogin(ctx, loginURL, username, password)
43+
finalURL, err := performLogin(ctx, loginURL, username, password, headless)
4244
if err != nil {
4345
if headless {
44-
log.Warn().Err(err).Msg("Headless login failed, retrying with window mode")
46+
log.Warn().Err(err).Msg("Headless login failed, retrying with window mode.")
47+
fmt.Println("Headless login failed, retrying with window mode.")
4548
// Retry with window mode if headless login fails
4649
ctx, cancel = createChromeContext(false)
4750
defer cancel()
4851

49-
finalURL, err = performLogin(ctx, loginURL, username, password)
52+
finalURL, err = performLogin(ctx, loginURL, username, password, false)
5053
if err != nil {
5154
return fmt.Errorf("failed to login: %w", err)
5255
}
@@ -86,7 +89,11 @@ func RefreshToken() (*db.Token, error) {
8689
return nil, fmt.Errorf("failed to retrieve token record: %w", err)
8790
}
8891

89-
if !isTokenValid(token) {
92+
// Check if the token is valid and refresh it if necessary
93+
tokenStatus, err := isTokenValid(token)
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to check token validity: %w", err)
96+
} else if !tokenStatus {
9097
if err := refreshAccessToken(token); err != nil {
9198
return nil, fmt.Errorf("failed to refresh token: %w", err)
9299
}
@@ -135,23 +142,24 @@ func refreshAccessToken(token *db.Token) error {
135142

136143
// isTokenValid checks if the access token (stored in the database) is still valid.
137144
// It takes a pointer to the token record and returns a boolean indicating whether the token is valid.
138-
func isTokenValid(token *db.Token) bool {
145+
func isTokenValid(token *db.Token) (bool, error) {
139146

140147
if token == nil {
141-
return false
148+
return false, fmt.Errorf("access token data does not exist in the database. Please login first")
142149
}
143150

144-
if token.AccessToken == "" || token.ExpiresAt == "" {
145-
return false
151+
// Check if the token fields are empty (needs refresh)
152+
if token.AccessToken == "" || token.RefreshToken == "" || token.ExpiresAt == "" {
153+
return false, nil
146154
}
147155

148156
expiresAt, err := time.Parse(time.RFC3339, token.ExpiresAt)
149157
if err != nil {
150-
log.Error().Err(err).Msg("Invalid expiration time format")
151-
return false
158+
log.Error().Msgf("Failed to parse expiration time: %s", token.ExpiresAt)
159+
return false, err
160+
} else {
161+
return time.Now().Before(expiresAt), nil
152162
}
153-
154-
return time.Now().Before(expiresAt)
155163
}
156164

157165
// createChromeContext creates a new ChromeDP context with the specified option to run Chrome in headless mode or not.
@@ -188,12 +196,27 @@ func createChromeContext(headless bool) (context.Context, context.CancelFunc) {
188196
}
189197

190198
// performLogin performs the login process using the provided username and password and returns the final URL after successful login.
191-
// It takes the ChromeDP context, login URL, username, and password as parameters and returns the final URL and an error if the login process fails.
192-
func performLogin(ctx context.Context, loginURL string, username string, password string) (string, error) {
193-
timeoutCtx, cancel := context.WithTimeout(ctx, 4*time.Minute)
194-
defer cancel()
199+
// It takes the ChromeDP context, login URL, username, password, and a boolean indicating whether to use headless mode as parameters, and returns the final URL and an error if the login process fails.
200+
func performLogin(ctx context.Context, loginURL string, username string, password string,
201+
headlessMode bool) (string, error) {
195202

203+
var timeoutCtx context.Context
204+
var cancel context.CancelFunc
196205
var finalURL string
206+
207+
if headlessMode {
208+
// Timeout for login in headless mode 30
209+
timeoutCtx, cancel = context.WithTimeout(ctx, 30*time.Second)
210+
defer cancel()
211+
} else {
212+
// Timeout for login in window mode 4 minutes
213+
timeoutCtx, cancel = context.WithTimeout(ctx, 4*time.Minute)
214+
}
215+
216+
// Close the context when the function returns
217+
defer cancel()
218+
219+
// Start the login process
197220
err := chromedp.Run(timeoutCtx,
198221
chromedp.Navigate(loginURL),
199222
chromedp.WaitVisible(`#login_username`, chromedp.ByID),

cmd/catalogue.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func listGames(cmd *cobra.Command, args []string) {
6464

6565
// Check if there are any games to display
6666
if len(games) == 0 {
67-
cmd.Println("No games found in the catalogue. Use `gogg catalogue refresh` to update the catalogue.")
67+
cmd.Println("Game catalogue is empty. Did you refresh the catalogue?")
6868
return
6969
}
7070

@@ -137,7 +137,7 @@ func showGameInfo(cmd *cobra.Command, gameID int) {
137137
// Check if the game was found
138138
if game == nil {
139139
log.Info().Msgf("No game found with ID=%d", gameID)
140-
cmd.Println("No game found with the specified ID.")
140+
cmd.Println("No game found with the specified ID. Please check the game ID.")
141141
return
142142
}
143143

@@ -196,11 +196,7 @@ func refreshCatalogue(cmd *cobra.Command, numThreads int) {
196196

197197
// Try to refresh the access token
198198
token, err := client.RefreshToken()
199-
if err != nil {
200-
cmd.PrintErrln("Error: Failed to refresh the access token. Please login again.")
201-
}
202-
203-
if token == nil {
199+
if err != nil || token == nil {
204200
cmd.PrintErrln("Error: Failed to refresh the access token. Did you login?")
205201
return
206202
}
@@ -387,13 +383,6 @@ func exportCmd() *cobra.Command {
387383
func exportCatalogue(cmd *cobra.Command, exportPath, exportFormat string) {
388384
log.Info().Msg("Exporting the game catalogue...")
389385

390-
// Ensure the directory exists or create it
391-
if err := os.MkdirAll(exportPath, os.ModePerm); err != nil {
392-
log.Error().Err(err).Msg("Failed to create export directory.")
393-
cmd.PrintErrln("Error: Failed to create export directory.")
394-
return
395-
}
396-
397386
// Validate the export format
398387
if exportFormat != "json" && exportFormat != "csv" {
399388
log.Error().Msg("Invalid export format. Supported formats: json, csv")
@@ -427,7 +416,7 @@ func exportCatalogue(cmd *cobra.Command, exportPath, exportFormat string) {
427416
return
428417
}
429418

430-
log.Info().Msgf("Game catalogue exported successfully to %s.", filePath)
419+
cmd.Printf("Game catalogue exported successfully to: \"%s\"\n", filePath)
431420
}
432421

433422
// exportCatalogueToCSV exports the game catalogue to a CSV file.
@@ -438,6 +427,13 @@ func exportCatalogueToCSV(path string) error {
438427
games, err := db.GetCatalogue()
439428
if err != nil {
440429
return err
430+
} else if len(games) == 0 {
431+
fmt.Println("No games found to export. Did you refresh the catalogue?")
432+
}
433+
434+
// Make sure the directory of the path exists
435+
if err := ensurePathExists(path); err != nil {
436+
return err
441437
}
442438

443439
// writeGamesToCSV writes the games to a CSV file.
@@ -479,6 +475,14 @@ func exportCatalogueToJSON(path string) error {
479475
games, err := db.GetCatalogue()
480476
if err != nil {
481477
return err
478+
} else if len(games) == 0 {
479+
fmt.Println("No games found to export. Did you refresh the catalogue?")
480+
return nil
481+
}
482+
483+
// Make sure the directory of the path exists
484+
if err := ensurePathExists(path); err != nil {
485+
return err
482486
}
483487

484488
// writeGamesToJSON writes the games to a JSON file.
@@ -503,3 +507,15 @@ func exportCatalogueToJSON(path string) error {
503507
// Write the full game catalogue to a JSON file
504508
return writeGamesToJSON(path, games)
505509
}
510+
511+
// ensurePathExists checks if the directory of the given path exists and creates it if it doesn't.
512+
// It takes a string representing the path and returns an error if any.
513+
func ensurePathExists(path string) error {
514+
if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) {
515+
if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
516+
log.Error().Err(err).Msgf("Failed to create directory %s", filepath.Dir(path))
517+
return err
518+
}
519+
}
520+
return nil
521+
}

cmd/download.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func executeDownload(gameID int, downloadPath, language, platformName string, ex
8080

8181
// Try to refresh the access token
8282
if _, err := client.RefreshToken(); err != nil {
83-
log.Error().Msg("Failed to refresh the access token. Please login again.")
83+
fmt.Println("Failed to find or refresh the access token. Did you login?")
8484
return
8585
}
8686

cmd/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66

77
var (
88
// version holds the current version of the Gogg.
9-
version = "0.3.0"
9+
version = "0.3.1"
1010
)
1111

1212
// versionCmd creates a new cobra.Command that shows the version of Gogg.

db/db.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func InitDB() error {
2929
return err
3030
}
3131

32+
// Configure the GORM logger
3233
configureLogger()
3334

3435
log.Info().Msg("Database initialized successfully")
@@ -39,7 +40,7 @@ func InitDB() error {
3940
// It returns an error if the directory creation fails.
4041
func createDBDirectory() error {
4142
if _, err := os.Stat(filepath.Dir(Path)); os.IsNotExist(err) {
42-
if err := os.MkdirAll(filepath.Dir(Path), 0755); err != nil {
43+
if err := os.MkdirAll(filepath.Dir(Path), 0750); err != nil {
4344
log.Error().Err(err).Msg("Failed to create database directory")
4445
return err
4546
}
@@ -94,3 +95,5 @@ func CloseDB() error {
9495
}
9596
return sqlDB.Close()
9697
}
98+
99+
//

docs/README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,16 @@ gogg download <game_id> <download_dir>
102102
The `download` command supports the following additional options:
103103

104104
- `--platform`: Filter the files to be downloaded by platform (all, windows, mac, linux) (default is windows)
105-
- `--lang`: Filter the files to be downloaded by language (all, en, fr, de, es, it, pl, ru, zh) (default is en)
105+
- `--lang`: Filter the files to be downloaded by language (en, fr, de, es, it, ru, pl, pt-BR, zh-Hans, ja, ko) (default
106+
is en)
106107
- `--dlcs`: Include DLC files in the download (default is true)
107108
- `--extras`: Include extra files in the download like soundtracks, wallpapers, etc. (default is true)
108109
- `--resume`: Resume interrupted downloads (default is true)
109110
- `--threads`: Number of worker threads to use for downloading (default is 5)
110111
- `--flatten`: Flatten the directory structure of the downloaded files (default is true)
111112

112113
For example, to download all files (English language) of a game with the ID `<game_id>` to the directory
113-
`<download_dir>`
114-
with the specified options:
114+
`<download_dir>` with the specified options:
115115

116116
```sh
117117
gogg download <game_id> <download_dir> --platform=all --lang=en --dlcs=true --extras=true \
@@ -123,7 +123,15 @@ gogg download <game_id> <download_dir> --platform=all --lang=en --dlcs=true --ex
123123
To enable debug mode, set the `DEBUG_GOGG` environment variable to `true` or `1` when running Gogg.
124124
In debug mode, Gogg will be much more verbose and print a lot of information to the console.
125125

126+
#### Linux and macOS
127+
126128
```sh
127129
DEBUG_GOGG=true gogg <command>
128130
```
129131

132+
#### Windows (PowerShell)
133+
134+
```powershell
135+
$env:DEBUG_GOGG = "true"; gogg <command>
136+
```
137+

0 commit comments

Comments
 (0)