diff --git a/cmd/heartbeat/heartbeat.go b/cmd/heartbeat/heartbeat.go index 8799a8e2..fce3b447 100644 --- a/cmd/heartbeat/heartbeat.go +++ b/cmd/heartbeat/heartbeat.go @@ -31,7 +31,7 @@ import ( // Run executes the heartbeat command. func Run(v *viper.Viper) (int, error) { - queueFilepath, err := offline.QueueFilepath() + queueFilepath, err := offline.QueueFilepath(v) if err != nil { log.Warnf("failed to load offline queue filepath: %s", err) } @@ -115,10 +115,6 @@ func SendHeartbeats(v *viper.Viper, queueFilepath string) error { handleOpts := initHandleOptions(params) if !params.Offline.Disabled { - if params.Offline.QueueFile != "" { - queueFilepath = params.Offline.QueueFile - } - handleOpts = append(handleOpts, offline.WithQueue(queueFilepath)) } diff --git a/cmd/offline/offline.go b/cmd/offline/offline.go index d84ec422..3f146c20 100644 --- a/cmd/offline/offline.go +++ b/cmd/offline/offline.go @@ -43,10 +43,6 @@ func SaveHeartbeats(v *viper.Viper, heartbeats []heartbeat.Heartbeat, queueFilep handleOpts := initHandleOptions(params) - if params.Offline.QueueFile != "" { - queueFilepath = params.Offline.QueueFile - } - handleOpts = append(handleOpts, offline.WithQueue(queueFilepath)) sender := offline.Noop{} diff --git a/cmd/offlinecount/offlinecount.go b/cmd/offlinecount/offlinecount.go index 7a8e1798..75f67f26 100644 --- a/cmd/offlinecount/offlinecount.go +++ b/cmd/offlinecount/offlinecount.go @@ -3,7 +3,6 @@ package offlinecount import ( "fmt" - "github.com/wakatime/wakatime-cli/cmd/params" "github.com/wakatime/wakatime-cli/pkg/exitcode" "github.com/wakatime/wakatime-cli/pkg/offline" @@ -12,7 +11,7 @@ import ( // Run executes the offline-count command. func Run(v *viper.Viper) (int, error) { - queueFilepath, err := offline.QueueFilepath() + queueFilepath, err := offline.QueueFilepath(v) if err != nil { return exitcode.ErrGeneric, fmt.Errorf( "failed to load offline queue filepath: %s", @@ -20,12 +19,6 @@ func Run(v *viper.Viper) (int, error) { ) } - p := params.LoadOfflineParams(v) - - if p.QueueFile != "" { - queueFilepath = p.QueueFile - } - count, err := offline.CountHeartbeats(queueFilepath) if err != nil { fmt.Println(err) diff --git a/cmd/offlineprint/offlineprint.go b/cmd/offlineprint/offlineprint.go index ee28bdab..300c338d 100644 --- a/cmd/offlineprint/offlineprint.go +++ b/cmd/offlineprint/offlineprint.go @@ -15,7 +15,7 @@ import ( // Run executes the print-offline-heartbeats command. func Run(v *viper.Viper) (int, error) { - queueFilepath, err := offline.QueueFilepath() + queueFilepath, err := offline.QueueFilepath(v) if err != nil { return exitcode.ErrGeneric, fmt.Errorf( "failed to load offline queue filepath: %s", @@ -25,10 +25,6 @@ func Run(v *viper.Viper) (int, error) { p := params.LoadOfflineParams(v) - if p.QueueFile != "" { - queueFilepath = p.QueueFile - } - hh, err := offline.ReadHeartbeats(queueFilepath, p.PrintMax) if err != nil { fmt.Println(err) diff --git a/cmd/offlinesync/offlinesync.go b/cmd/offlinesync/offlinesync.go index 878834e5..35eb5e31 100644 --- a/cmd/offlinesync/offlinesync.go +++ b/cmd/offlinesync/offlinesync.go @@ -44,7 +44,7 @@ func run(v *viper.Viper) (int, error) { return exitcode.Success, nil } - queueFilepath, err := offline.QueueFilepath() + queueFilepath, err := offline.QueueFilepath(v) if err != nil { return exitcode.ErrGeneric, fmt.Errorf( "offline sync failed: failed to load offline queue filepath: %s", @@ -52,7 +52,7 @@ func run(v *viper.Viper) (int, error) { ) } - queueFilepathLegacy, err := offline.QueueFilepathLegacy() + queueFilepathLegacy, err := offline.QueueFilepathLegacy(v) if err != nil { log.Warnf("legacy offline sync failed: failed to load offline queue filepath: %s", err) } @@ -84,6 +84,10 @@ func syncOfflineActivityLegacy(v *viper.Viper, queueFilepath string) error { return nil } + if !fileExists(queueFilepath) { + return nil + } + paramOffline := params.LoadOfflineParams(v) paramAPI, err := params.LoadAPIParams(v) @@ -96,10 +100,6 @@ func syncOfflineActivityLegacy(v *viper.Viper, queueFilepath string) error { return fmt.Errorf("failed to initialize api client: %w", err) } - if paramOffline.QueueFileLegacy != "" { - queueFilepath = paramOffline.QueueFileLegacy - } - handle := heartbeat.NewHandle(apiClient, offline.WithSync(queueFilepath, paramOffline.SyncMax), apikey.WithReplacing(apikey.Config{ @@ -135,10 +135,6 @@ func SyncOfflineActivity(v *viper.Viper, queueFilepath string) error { paramOffline := params.LoadOfflineParams(v) - if paramOffline.QueueFile != "" { - queueFilepath = paramOffline.QueueFile - } - handle := heartbeat.NewHandle(apiClient, offline.WithSync(queueFilepath, paramOffline.SyncMax), apikey.WithReplacing(apikey.Config{ @@ -158,3 +154,9 @@ func SyncOfflineActivity(v *viper.Viper, queueFilepath string) error { return nil } + +// fileExists checks if a file or directory exist. +func fileExists(fp string) bool { + _, err := os.Stat(fp) + return err == nil || os.IsExist(err) +} diff --git a/cmd/offlinesync/offlinesync_test.go b/cmd/offlinesync/offlinesync_test.go index cad6f803..51f2f8f7 100644 --- a/cmd/offlinesync/offlinesync_test.go +++ b/cmd/offlinesync/offlinesync_test.go @@ -311,97 +311,6 @@ func TestSyncOfflineActivity(t *testing.T) { assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) } -func TestSyncOfflineActivity_QueueFileFromConfig(t *testing.T) { - resetSingleton(t) - - testServerURL, router, tearDown := setupTestServer() - defer tearDown() - - var ( - plugin = "plugin/0.0.1" - numCalls int - ) - - router.HandleFunc("/users/current/heartbeats.bulk", func(w http.ResponseWriter, req *http.Request) { - numCalls++ - - // check request - assert.Equal(t, http.MethodPost, req.Method) - assert.Equal(t, []string{"application/json"}, req.Header["Accept"]) - assert.Equal(t, []string{"application/json"}, req.Header["Content-Type"]) - assert.Equal(t, []string{"Basic MDAwMDAwMDAtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAw"}, req.Header["Authorization"]) - assert.True(t, strings.HasSuffix(req.Header["User-Agent"][0], plugin), fmt.Sprintf( - "%q should have suffix %q", - req.Header["User-Agent"][0], - plugin, - )) - - expectedBody, err := os.ReadFile("testdata/api_heartbeats_request_template.json") - require.NoError(t, err) - - body, err := io.ReadAll(req.Body) - require.NoError(t, err) - - assert.JSONEq(t, string(expectedBody), string(body)) - - // send response - w.WriteHeader(http.StatusCreated) - - f, err := os.Open("testdata/api_heartbeats_response.json") - require.NoError(t, err) - defer f.Close() - - _, err = io.Copy(w, f) - require.NoError(t, err) - }) - - // setup offline queue - f, err := os.CreateTemp(t.TempDir(), "") - require.NoError(t, err) - - db, err := bolt.Open(f.Name(), 0600, nil) - require.NoError(t, err) - - dataGo, err := os.ReadFile("testdata/heartbeat_go.json") - require.NoError(t, err) - - dataPy, err := os.ReadFile("testdata/heartbeat_py.json") - require.NoError(t, err) - - dataJs, err := os.ReadFile("testdata/heartbeat_js.json") - require.NoError(t, err) - - insertHeartbeatRecords(t, db, "heartbeats", []heartbeatRecord{ - { - ID: "1592868367.219124-file-coding-wakatime-cli-heartbeat-/tmp/main.go-true", - Heartbeat: string(dataGo), - }, - { - ID: "1592868386.079084-file-debugging-wakatime-summary-/tmp/main.py-false", - Heartbeat: string(dataPy), - }, - { - ID: "1592868394.084354-file-building-wakatime-todaygoal-/tmp/main.js-false", - Heartbeat: string(dataJs), - }, - }) - - err = db.Close() - require.NoError(t, err) - - v := viper.New() - v.Set("api-url", testServerURL) - v.Set("key", "00000000-0000-4000-8000-000000000000") - v.Set("offline-queue-file", f.Name()) - v.Set("sync-offline-activity", 100) - v.Set("plugin", plugin) - - err = offlinesync.SyncOfflineActivity(v, "/another/file") - require.NoError(t, err) - - assert.Eventually(t, func() bool { return numCalls == 1 }, time.Second, 50*time.Millisecond) -} - func TestSyncOfflineActivity_MultipleApiKey(t *testing.T) { resetSingleton(t) diff --git a/cmd/params/params.go b/cmd/params/params.go index abced5c9..eee50074 100644 --- a/cmd/params/params.go +++ b/cmd/params/params.go @@ -131,13 +131,11 @@ type ( // Offline contains offline related parameters. Offline struct { - Disabled bool - LastSentAt time.Time - PrintMax int - QueueFile string - QueueFileLegacy string - RateLimit time.Duration - SyncMax int + Disabled bool + LastSentAt time.Time + PrintMax int + RateLimit time.Duration + SyncMax int } // ProjectParams params for project name sanitization. @@ -674,13 +672,11 @@ func LoadOfflineParams(v *viper.Viper) Offline { } return Offline{ - Disabled: disabled, - LastSentAt: lastSentAt, - PrintMax: v.GetInt("print-offline-heartbeats"), - QueueFile: vipertools.GetString(v, "offline-queue-file"), - QueueFileLegacy: vipertools.GetString(v, "offline-queue-file-legacy"), - RateLimit: time.Duration(rateLimit) * time.Second, - SyncMax: syncMax, + Disabled: disabled, + LastSentAt: lastSentAt, + PrintMax: v.GetInt("print-offline-heartbeats"), + RateLimit: time.Duration(rateLimit) * time.Second, + SyncMax: syncMax, } } @@ -1074,13 +1070,10 @@ func (p Offline) String() string { } return fmt.Sprintf( - "disabled: %t, last sent at: '%s', print max: %d, queue file: '%s', queue file legacy: '%s',"+ - " num rate limit: %d, num sync max: %d", + "disabled: %t, last sent at: '%s', print max: %d, num rate limit: %d, num sync max: %d", p.Disabled, lastSentAt, p.PrintMax, - p.QueueFile, - p.QueueFileLegacy, p.RateLimit, p.SyncMax, ) diff --git a/cmd/params/params_test.go b/cmd/params/params_test.go index 7138cb1f..7e31d4c5 100644 --- a/cmd/params/params_test.go +++ b/cmd/params/params_test.go @@ -1771,24 +1771,6 @@ func TestLoadOfflineParams_LastSentAt_Err(t *testing.T) { assert.Zero(t, params.LastSentAt) } -func TestLoadOfflineParams_QueueFile(t *testing.T) { - v := viper.New() - v.Set("offline-queue-file", "/path/to/file") - - params := cmdparams.LoadOfflineParams(v) - - assert.Equal(t, "/path/to/file", params.QueueFile) -} - -func TestLoadOfflineParams_QueueFileLegacy(t *testing.T) { - v := viper.New() - v.Set("offline-queue-file-legacy", "/path/to/file") - - params := cmdparams.LoadOfflineParams(v) - - assert.Equal(t, "/path/to/file", params.QueueFileLegacy) -} - func TestLoadOfflineParams_SyncMax(t *testing.T) { v := viper.New() v.Set("sync-offline-activity", 42) @@ -2552,19 +2534,16 @@ func TestOffline_String(t *testing.T) { require.NoError(t, err) offline := cmdparams.Offline{ - Disabled: true, - LastSentAt: lastSentAt, - PrintMax: 6, - QueueFile: "/path/to/queue.file", - QueueFileLegacy: "/path/to/legacy.file", - RateLimit: 15, - SyncMax: 12, + Disabled: true, + LastSentAt: lastSentAt, + PrintMax: 6, + RateLimit: 15, + SyncMax: 12, } assert.Equal( t, "disabled: true, last sent at: '2021-08-30T18:50:42-03:00', print max: 6,"+ - " queue file: '/path/to/queue.file', queue file legacy: '/path/to/legacy.file',"+ " num rate limit: 15, num sync max: 12", offline.String(), ) diff --git a/cmd/run.go b/cmd/run.go index 73b4f97a..7774e0a1 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -349,7 +349,7 @@ func runCmd(v *viper.Viper, verbose bool, sendDiagsOnErrors bool, cmd cmdFn) (er } func saveHeartbeats(v *viper.Viper) int { - queueFilepath, err := offline.QueueFilepath() + queueFilepath, err := offline.QueueFilepath(v) if err != nil { log.Warnf("failed to load offline queue filepath: %s", err) } diff --git a/main_test.go b/main_test.go index 422c8a2c..dbf965e5 100644 --- a/main_test.go +++ b/main_test.go @@ -373,7 +373,7 @@ func TestSendHeartbeats_ExtraHeartbeats_SyncLegacyOfflineActivity(t *testing.T) tmpDir := t.TempDir() // create legacy offline queue file and add some heartbeats - offlineQueueFileLegacy, err := os.CreateTemp(tmpDir, "") + offlineQueueFileLegacy, err := os.CreateTemp(tmpDir, "legacy-offline-file") require.NoError(t, err) // early close to avoid file locking in Windows @@ -409,7 +409,7 @@ func TestSendHeartbeats_ExtraHeartbeats_SyncLegacyOfflineActivity(t *testing.T) err = db.Close() require.NoError(t, err) - offlineQueueFile, err := os.CreateTemp(tmpDir, "") + offlineQueueFile, err := os.CreateTemp(tmpDir, "new-offline-file") require.NoError(t, err) defer offlineQueueFile.Close() @@ -429,6 +429,8 @@ func TestSendHeartbeats_ExtraHeartbeats_SyncLegacyOfflineActivity(t *testing.T) buffer := bytes.NewBuffer(data) + assert.FileExists(t, offlineQueueFileLegacy.Name()) + runWakatimeCli( t, buffer, @@ -439,11 +441,11 @@ func TestSendHeartbeats_ExtraHeartbeats_SyncLegacyOfflineActivity(t *testing.T) "--entity", "testdata/main.go", "--extra-heartbeats", "true", "--cursorpos", "12", - "--sync-offline-activity", "0", "--offline-queue-file", offlineQueueFile.Name(), "--offline-queue-file-legacy", offlineQueueFileLegacy.Name(), "--lineno", "42", "--lines-in-file", "100", + "--heartbeat-rate-limit-seconds", "0", "--time", "1585598059", "--hide-branch-names", ".*", "--write", diff --git a/pkg/offline/legacy.go b/pkg/offline/legacy.go index ea50534d..8e2d859e 100644 --- a/pkg/offline/legacy.go +++ b/pkg/offline/legacy.go @@ -4,7 +4,10 @@ import ( "fmt" "path/filepath" + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" "github.com/wakatime/wakatime-cli/pkg/ini" + "github.com/wakatime/wakatime-cli/pkg/vipertools" ) // dbLegacyFilename is the legacy bolt db filename. @@ -14,7 +17,17 @@ const dbLegacyFilename = ".wakatime.bdb" // the user's $HOME folder cannot be detected, it defaults to the // current directory. // This is used to support the old db file name and will be removed in the future. -func QueueFilepathLegacy() (string, error) { +func QueueFilepathLegacy(v *viper.Viper) (string, error) { + paramFile := vipertools.GetString(v, "offline-queue-file-legacy") + if paramFile != "" { + p, err := homedir.Expand(paramFile) + if err != nil { + return "", fmt.Errorf("failed expanding offline-queue-file-legacy param: %s", err) + } + + return p, nil + } + home, _, err := ini.WakaHomeDir() if err != nil { return dbFilename, fmt.Errorf("failed getting user's home directory, defaulting to current directory: %s", err) diff --git a/pkg/offline/legacy_test.go b/pkg/offline/legacy_test.go index 86787452..c2784549 100644 --- a/pkg/offline/legacy_test.go +++ b/pkg/offline/legacy_test.go @@ -7,6 +7,7 @@ import ( "github.com/wakatime/wakatime-cli/pkg/offline" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -40,7 +41,8 @@ func TestQueueFilepathLegacy(t *testing.T) { defer os.Unsetenv("WAKATIME_HOME") - queueFilepath, err := offline.QueueFilepathLegacy() + v := viper.New() + queueFilepath, err := offline.QueueFilepathLegacy(v) require.NoError(t, err) assert.Equal(t, test.Expected, queueFilepath) diff --git a/pkg/offline/offline.go b/pkg/offline/offline.go index 1f4ed907..f4ab8ab1 100644 --- a/pkg/offline/offline.go +++ b/pkg/offline/offline.go @@ -9,10 +9,13 @@ import ( "path/filepath" "time" + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" "github.com/wakatime/wakatime-cli/pkg/api" "github.com/wakatime/wakatime-cli/pkg/heartbeat" "github.com/wakatime/wakatime-cli/pkg/ini" "github.com/wakatime/wakatime-cli/pkg/log" + "github.com/wakatime/wakatime-cli/pkg/vipertools" bolt "go.etcd.io/bbolt" ) @@ -91,7 +94,17 @@ func WithQueue(filepath string) heartbeat.HandleOption { // QueueFilepath returns the path for offline queue db file. If // the resource directory cannot be detected, it defaults to the // current directory. -func QueueFilepath() (string, error) { +func QueueFilepath(v *viper.Viper) (string, error) { + paramFile := vipertools.GetString(v, "offline-queue-file") + if paramFile != "" { + p, err := homedir.Expand(paramFile) + if err != nil { + return "", fmt.Errorf("failed expanding offline-queue-file param: %s", err) + } + + return p, nil + } + folder, err := ini.WakaResourcesDir() if err != nil { return dbFilename, fmt.Errorf("failed getting resource directory, defaulting to current directory: %s", err) diff --git a/pkg/offline/offline_test.go b/pkg/offline/offline_test.go index 7acac098..6d150d33 100644 --- a/pkg/offline/offline_test.go +++ b/pkg/offline/offline_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/spf13/viper" "github.com/wakatime/wakatime-cli/pkg/heartbeat" "github.com/wakatime/wakatime-cli/pkg/ini" "github.com/wakatime/wakatime-cli/pkg/offline" @@ -43,7 +44,8 @@ func TestQueueFilepath(t *testing.T) { folder, err := ini.WakaResourcesDir() require.NoError(t, err) - queueFilepath, err := offline.QueueFilepath() + v := viper.New() + queueFilepath, err := offline.QueueFilepath(v) require.NoError(t, err) expected := filepath.Join(folder, "offline_heartbeats.bdb")