diff --git a/.gitignore b/.gitignore index e9a7666..4a67232 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.dll *.so *.dylib +timetrace # IDE directories .idea diff --git a/cli/delete.go b/cli/delete.go index d4cc368..c633651 100644 --- a/cli/delete.go +++ b/cli/delete.go @@ -147,7 +147,7 @@ func deleteRecordCommand(t *core.Timetrace) *cobra.Command { return } - if err := t.BackupRecord(start); err != nil { + if err := t.BackupRecord(*record); err != nil { out.Err("failed to backup record before deletion: %s", err.Error()) return } @@ -167,10 +167,10 @@ func deleteRecordCommand(t *core.Timetrace) *cobra.Command { } func askForConfirmation(msg string) bool { - reader := bufio.NewReader(os.Stdin) + scanner := bufio.NewScanner(os.Stdin) fmt.Fprint(os.Stderr, msg) - s, _ := reader.ReadString('\n') - s = strings.TrimSuffix(s, "\n") + scanner.Scan() + s := scanner.Text() s = strings.ToLower(s) return s == "y" diff --git a/cli/edit.go b/cli/edit.go index 6262e63..db24b85 100644 --- a/cli/edit.go +++ b/cli/edit.go @@ -2,6 +2,7 @@ package cli import ( "errors" + "fmt" "strconv" "strings" "time" @@ -83,11 +84,27 @@ func editRecordCommand(t *core.Timetrace) *cobra.Command { return } - recordTime, err := getRecordTimeFromArg(t, args[0]) - - if err != nil { - out.Err(err.Error()) - return + var recordTime time.Time + var err error + var rec *core.Record + // if more aliases are needed, this should be expanded to a switch + if strings.ToLower(args[0]) == "latest" { + rec, err = t.LoadLatestRecord() + if err != nil { + out.Err("Error on loading last record: %s", err.Error()) + return + } + recordTime = rec.Start + } else { + recordTime, err = t.Formatter().ParseRecordKey(args[0]) + if err != nil { + out.Err("Failed to parse date argument: %s", err.Error()) + return + } + rec, err = t.LoadRecord(recordTime) + if err != nil { + return + } } if options.Revert { @@ -99,12 +116,17 @@ func editRecordCommand(t *core.Timetrace) *cobra.Command { return } - if err := t.BackupRecord(recordTime); err != nil { + if err := t.BackupRecord(*rec); err != nil { out.Err("failed to backup record before edit: %s", err.Error()) return } if options.Minus == "" && options.Plus == "" { + fmt.Printf("Warning: Directly editing the record can lead to undefined behavior.\nIf you change the start time of the record, the underlying file will be renamed automatically. Make sure it doesn't collide with other records. If you want to change the end time, use --minus or --plus instead.\nContinue? ") + if !askForConfirmation("y/[n]") { + out.Info("Editting record aborted.") + return + } out.Info("Opening %s in default editor", recordTime) if err := t.EditRecordManual(recordTime); err != nil { out.Err("failed to edit record: %s", err.Error()) diff --git a/core/record.go b/core/record.go index d69a619..b0b7403 100644 --- a/core/record.go +++ b/core/record.go @@ -87,14 +87,9 @@ func (t *Timetrace) SaveRecord(record Record, force bool) error { } // BackupRecord creates a backup of the given record file -func (t *Timetrace) BackupRecord(recordKey time.Time) error { - path := t.fs.RecordFilepath(recordKey) - record, err := t.loadRecord(path) - if err != nil { - return err - } +func (t *Timetrace) BackupRecord(record Record) error { // create a new .bak filepath from the record struct - backupPath := t.fs.RecordBackupFilepath(recordKey) + backupPath := t.fs.RecordBackupFilepath(record.Start) backupFile, err := os.OpenFile(backupPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { @@ -117,9 +112,19 @@ func (t *Timetrace) RevertRecord(recordKey time.Time) error { return err } - path := t.fs.RecordFilepath(recordKey) + oldPath := t.fs.RecordFilepath(recordKey) + newPath := t.fs.RecordFilepath(record.Start) + if err = os.Rename(oldPath, newPath); err != nil { + return err + } - file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + oldBackupPath := t.fs.RecordBackupFilepath(recordKey) + newBackupPath := t.fs.RecordBackupFilepath(record.Start) + if err = os.Rename(oldBackupPath, newBackupPath); err != nil { + return err + } + + file, err := os.OpenFile(newPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } @@ -235,7 +240,7 @@ func (t *Timetrace) DeleteRecordsByProject(key string) error { if record.Project.Key != k { continue } - if err := t.BackupRecord(record.Start); err != nil { + if err := t.BackupRecord(*record); err != nil { return err } t.DeleteRecord(*record) @@ -246,12 +251,16 @@ func (t *Timetrace) DeleteRecordsByProject(key string) error { } // EditRecordManual opens the record file in the preferred or default editor. +// note we don't rename the backup, because it could have inconsistency between +// start time and file name func (t *Timetrace) EditRecordManual(recordTime time.Time) error { path := t.fs.RecordFilepath(recordTime) - if _, err := t.loadRecord(path); err != nil { + rec, err := t.loadRecord(path) + if err != nil { return err } + originalStart := rec.Start editor := t.editorFromEnvironment() cmd := exec.Command(editor, path) @@ -259,7 +268,23 @@ func (t *Timetrace) EditRecordManual(recordTime time.Time) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - return cmd.Run() + if err = cmd.Run(); err != nil { + return err + } + + edittedRec, err := t.loadRecord(path) + if err != nil { + return err + } + + newStart := edittedRec.Start + if originalStart == newStart { + return nil + } + + newPath := t.fs.RecordFilepath(newStart) + + return os.Rename(path, newPath) } // EditRecord loads the record internally, applies the option values and saves the record diff --git a/timetrace b/timetrace deleted file mode 100755 index 7839be1..0000000 Binary files a/timetrace and /dev/null differ