diff --git a/pkg/cli/.gitignore b/pkg/cli/.gitignore
index 7de9e987..87f89ac4 100644
--- a/pkg/cli/.gitignore
+++ b/pkg/cli/.gitignore
@@ -1,6 +1,6 @@
main
*.swo
-tmp/
+tmp*/
/vendor
test-dnote
/dist
diff --git a/pkg/cli/cmd/add/add.go b/pkg/cli/cmd/add/add.go
index 11be9ec0..ab237267 100644
--- a/pkg/cli/cmd/add/add.go
+++ b/pkg/cli/cmd/add/add.go
@@ -115,8 +115,12 @@ func newRun(ctx context.DnoteCtx) infra.RunEFunc {
}
if content == "" {
- fpath := ui.GetTmpContentPath(ctx)
- err := ui.GetEditorInput(ctx, fpath, &content)
+ fpath, err := ui.GetTmpContentPath(ctx)
+ if err != nil {
+ return errors.Wrap(err, "getting temporarily content file path")
+ }
+
+ err = ui.GetEditorInput(ctx, fpath, &content)
if err != nil {
return errors.Wrap(err, "Failed to get editor input")
}
diff --git a/pkg/cli/cmd/edit/edit.go b/pkg/cli/cmd/edit/edit.go
index 4ec1daa9..cdc4210a 100644
--- a/pkg/cli/cmd/edit/edit.go
+++ b/pkg/cli/cmd/edit/edit.go
@@ -89,7 +89,10 @@ func newRun(ctx context.DnoteCtx) infra.RunEFunc {
}
if newContent == "" {
- fpath := ui.GetTmpContentPath(ctx)
+ fpath, err := ui.GetTmpContentPath(ctx)
+ if err != nil {
+ return errors.Wrap(err, "getting temporarily content file path")
+ }
e := ioutil.WriteFile(fpath, []byte(oldContent), 0644)
if e != nil {
diff --git a/pkg/cli/consts/consts.go b/pkg/cli/consts/consts.go
index 3212dfcd..46f196da 100644
--- a/pkg/cli/consts/consts.go
+++ b/pkg/cli/consts/consts.go
@@ -24,8 +24,10 @@ var (
DnoteDirName = ".dnote"
// DnoteDBFileName is a filename for the Dnote SQLite database
DnoteDBFileName = "dnote.db"
- // TmpContentFilename is the filename for a temporary content
- TmpContentFilename = "DNOTE_TMPCONTENT.md"
+ // TmpContentFileBase is the base for the filename for a temporary content
+ TmpContentFileBase = "DNOTE_TMPCONTENT"
+ // TmpContentFileExt is the extension for the temporary content file
+ TmpContentFileExt = "md"
// ConfigFilename is the name of the config file
ConfigFilename = "dnoterc"
diff --git a/pkg/cli/infra/init.go b/pkg/cli/infra/init.go
index 6b278eb7..f038ec05 100644
--- a/pkg/cli/infra/init.go
+++ b/pkg/cli/infra/init.go
@@ -315,7 +315,11 @@ func getEditorCommand() string {
func initDnoteDir(ctx context.DnoteCtx) error {
path := ctx.DnoteDir
- if utils.FileExists(path) {
+ ok, err := utils.FileExists(path)
+ if err != nil {
+ return errors.Wrap(err, "checking if dnote dir exists")
+ }
+ if ok {
return nil
}
@@ -329,7 +333,11 @@ func initDnoteDir(ctx context.DnoteCtx) error {
// initConfigFile populates a new config file if it does not exist yet
func initConfigFile(ctx context.DnoteCtx, apiEndpoint string) error {
path := config.GetPath(ctx)
- if utils.FileExists(path) {
+ ok, err := utils.FileExists(path)
+ if err != nil {
+ return errors.Wrap(err, "checking if config exists")
+ }
+ if ok {
return nil
}
diff --git a/pkg/cli/main_test.go b/pkg/cli/main_test.go
index ae6e7aab..8bfc339d 100644
--- a/pkg/cli/main_test.go
+++ b/pkg/cli/main_test.go
@@ -57,10 +57,19 @@ func TestInit(t *testing.T) {
db := database.OpenTestDB(t, opts.DnoteDir)
// Test
- if !utils.FileExists(opts.DnoteDir) {
+ ok, err := utils.FileExists(opts.DnoteDir)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if dnote dir exists"))
+ }
+ if !ok {
t.Errorf("dnote directory was not initialized")
}
- if !utils.FileExists(fmt.Sprintf("%s/%s", opts.DnoteDir, consts.ConfigFilename)) {
+
+ ok, err = utils.FileExists(fmt.Sprintf("%s/%s", opts.DnoteDir, consts.ConfigFilename))
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if dnote config exists"))
+ }
+ if !ok {
t.Errorf("config file was not initialized")
}
diff --git a/pkg/cli/migrate/legacy.go b/pkg/cli/migrate/legacy.go
index ea537caa..e9105cc4 100644
--- a/pkg/cli/migrate/legacy.go
+++ b/pkg/cli/migrate/legacy.go
@@ -85,7 +85,11 @@ func makeSchema(complete bool) schema {
func Legacy(ctx context.DnoteCtx) error {
// If schema does not exist, no need run a legacy migration
schemaPath := getSchemaPath(ctx)
- if ok := utils.FileExists(schemaPath); !ok {
+ ok, err := utils.FileExists(schemaPath)
+ if err != nil {
+ return errors.Wrap(err, "checking if schema exists")
+ }
+ if !ok {
return nil
}
@@ -472,7 +476,11 @@ var migrateToV8SystemKeyBookMark = "bookmark"
// migrateToV1 deletes YAML archive if exists
func migrateToV1(ctx context.DnoteCtx) error {
yamlPath := fmt.Sprintf("%s/%s", ctx.HomeDir, ".dnote-yaml-archived")
- if !utils.FileExists(yamlPath) {
+ ok, err := utils.FileExists(yamlPath)
+ if err != nil {
+ return errors.Wrap(err, "checking if yaml file exists")
+ }
+ if !ok {
return nil
}
diff --git a/pkg/cli/migrate/legacy_test.go b/pkg/cli/migrate/legacy_test.go
index 012af095..fcda3399 100644
--- a/pkg/cli/migrate/legacy_test.go
+++ b/pkg/cli/migrate/legacy_test.go
@@ -72,7 +72,11 @@ func TestMigrateToV1(t *testing.T) {
}
// test
- if utils.FileExists(yamlPath) {
+ ok, err := utils.FileExists(yamlPath)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if yaml file exists"))
+ }
+ if ok {
t.Fatal("YAML archive file has not been deleted")
}
})
@@ -93,7 +97,11 @@ func TestMigrateToV1(t *testing.T) {
}
// test
- if utils.FileExists(yamlPath) {
+ ok, err := utils.FileExists(yamlPath)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if yaml file exists"))
+ }
+ if ok {
t.Fatal("YAML archive file must not exist")
}
})
@@ -372,23 +380,43 @@ func TestMigrateToV8(t *testing.T) {
dnotercPath := fmt.Sprintf("%s/dnoterc", ctx.DnoteDir)
schemaFilePath := fmt.Sprintf("%s/schema", ctx.DnoteDir)
timestampFilePath := fmt.Sprintf("%s/timestamps", ctx.DnoteDir)
- if ok := utils.FileExists(dnoteFilePath); ok {
+
+ ok, err := utils.FileExists(dnoteFilePath)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if file exists"))
+ }
+ if ok {
t.Errorf("%s still exists", dnoteFilePath)
}
- if ok := utils.FileExists(schemaFilePath); ok {
- t.Errorf("%s still exists", dnoteFilePath)
+
+ ok, err = utils.FileExists(schemaFilePath)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if file exists"))
}
- if ok := utils.FileExists(timestampFilePath); ok {
- t.Errorf("%s still exists", dnoteFilePath)
+ if ok {
+ t.Errorf("%s still exists", schemaFilePath)
}
- if ok := utils.FileExists(dnotercPath); !ok {
- t.Errorf("%s should exist", dnotercPath)
+
+ ok, err = utils.FileExists(timestampFilePath)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if file exists"))
+ }
+ if ok {
+ t.Errorf("%s still exists", timestampFilePath)
+ }
+
+ ok, err = utils.FileExists(dnotercPath)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "checking if file exists"))
+ }
+ if !ok {
+ t.Errorf("%s still exists", dnotercPath)
}
// 2. test if notes and books are migrated
var bookCount, noteCount int
- err := db.QueryRow("SELECT count(*) FROM books").Scan(&bookCount)
+ err = db.QueryRow("SELECT count(*) FROM books").Scan(&bookCount)
if err != nil {
panic(errors.Wrap(err, "counting books"))
}
diff --git a/pkg/cli/ui/editor.go b/pkg/cli/ui/editor.go
index bdee122c..5c0e1643 100644
--- a/pkg/cli/ui/editor.go
+++ b/pkg/cli/ui/editor.go
@@ -34,8 +34,19 @@ import (
// GetTmpContentPath returns the path to the temporary file containing
// content being added or edited
-func GetTmpContentPath(ctx context.DnoteCtx) string {
- return fmt.Sprintf("%s/%s", ctx.DnoteDir, consts.TmpContentFilename)
+func GetTmpContentPath(ctx context.DnoteCtx) (string, error) {
+ for i := 0; ; i++ {
+ filename := fmt.Sprintf("%s_%d.%s", consts.TmpContentFileBase, i, consts.TmpContentFileExt)
+ candidate := fmt.Sprintf("%s/%s", ctx.DnoteDir, filename)
+
+ ok, err := utils.FileExists(candidate)
+ if err != nil {
+ return "", errors.Wrapf(err, "checking if file exists at %s", candidate)
+ }
+ if !ok {
+ return candidate, nil
+ }
+ }
}
// getEditorCommand returns the system's editor command with appropriate flags,
@@ -91,7 +102,11 @@ func newEditorCmd(ctx context.DnoteCtx, fpath string) (*exec.Cmd, error) {
// GetEditorInput gets the user input by launching a text editor and waiting for
// it to exit
func GetEditorInput(ctx context.DnoteCtx, fpath string, content *string) error {
- if !utils.FileExists(fpath) {
+ ok, err := utils.FileExists(fpath)
+ if err != nil {
+ return errors.Wrapf(err, "checking if the file exists at %s", fpath)
+ }
+ if !ok {
f, err := os.Create(fpath)
if err != nil {
return errors.Wrap(err, "creating a temporary content file")
diff --git a/pkg/cli/ui/editor_test.go b/pkg/cli/ui/editor_test.go
new file mode 100644
index 00000000..9fcdb4a9
--- /dev/null
+++ b/pkg/cli/ui/editor_test.go
@@ -0,0 +1,90 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote CLI.
+ *
+ * Dnote CLI is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote CLI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Dnote CLI. If not, see .
+ */
+
+package ui
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/cli/context"
+ "github.com/pkg/errors"
+)
+
+func TestGetTmpContentPath(t *testing.T) {
+ t.Run("no collision", func(t *testing.T) {
+ ctx := context.InitTestCtx(t, "../tmp1", nil)
+ defer context.TeardownTestCtx(t, ctx)
+
+ res, err := GetTmpContentPath(ctx)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "executing"))
+ }
+
+ expected := fmt.Sprintf("%s/%s", ctx.DnoteDir, "DNOTE_TMPCONTENT_0.md")
+ assert.Equal(t, res, expected, "filename did not match")
+ })
+
+ t.Run("one existing session", func(t *testing.T) {
+ // set up
+ ctx := context.InitTestCtx(t, "../tmp2", nil)
+ defer context.TeardownTestCtx(t, ctx)
+
+ p := fmt.Sprintf("%s/%s", ctx.DnoteDir, "DNOTE_TMPCONTENT_0.md")
+ if _, err := os.Create(p); err != nil {
+ t.Fatal(errors.Wrap(err, "preparing the conflicting file"))
+ }
+
+ // execute
+ res, err := GetTmpContentPath(ctx)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "executing"))
+ }
+
+ // test
+ expected := fmt.Sprintf("%s/%s", ctx.DnoteDir, "DNOTE_TMPCONTENT_1.md")
+ assert.Equal(t, res, expected, "filename did not match")
+ })
+
+ t.Run("two existing sessions", func(t *testing.T) {
+ // set up
+ ctx := context.InitTestCtx(t, "../tmp3", nil)
+ defer context.TeardownTestCtx(t, ctx)
+
+ p1 := fmt.Sprintf("%s/%s", ctx.DnoteDir, "DNOTE_TMPCONTENT_0.md")
+ if _, err := os.Create(p1); err != nil {
+ t.Fatal(errors.Wrap(err, "preparing the conflicting file"))
+ }
+ p2 := fmt.Sprintf("%s/%s", ctx.DnoteDir, "DNOTE_TMPCONTENT_1.md")
+ if _, err := os.Create(p2); err != nil {
+ t.Fatal(errors.Wrap(err, "preparing the conflicting file"))
+ }
+
+ // execute
+ res, err := GetTmpContentPath(ctx)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "executing"))
+ }
+
+ // test
+ expected := fmt.Sprintf("%s/%s", ctx.DnoteDir, "DNOTE_TMPCONTENT_2.md")
+ assert.Equal(t, res, expected, "filename did not match")
+ })
+}
diff --git a/pkg/cli/utils/files.go b/pkg/cli/utils/files.go
index 452ee475..b5e2c501 100644
--- a/pkg/cli/utils/files.go
+++ b/pkg/cli/utils/files.go
@@ -44,9 +44,16 @@ func ReadFileAbs(relpath string) []byte {
}
// FileExists checks if the file exists at the given path
-func FileExists(filepath string) bool {
+func FileExists(filepath string) (bool, error) {
_, err := os.Stat(filepath)
- return !os.IsNotExist(err)
+ if err == nil {
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+
+ return false, errors.Wrap(err, "getting file info")
}
// CopyDir copies a directory from src to dest, recursively copying nested
diff --git a/pkg/server/database/migrate/main.go b/pkg/server/database/migrate/main.go
index ce132687..cf1207d5 100644
--- a/pkg/server/database/migrate/main.go
+++ b/pkg/server/database/migrate/main.go
@@ -1,3 +1,21 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
package main
import (