diff --git a/engine.go b/engine.go index e2557f6..d75299f 100644 --- a/engine.go +++ b/engine.go @@ -68,15 +68,13 @@ type OptionSetter func(*Engine) error // path is where the data files will be stored if the path doesn't exist it will be created // the user should have write access to the path otherwise an error will be returned func NewEngine(path string, options ...OptionSetter) (*Engine, error) { - - // create the data file directory if it doesn't exist - if err := os.MkdirAll(path, 0755); err != nil { + if err := validateDataPath(path); err != nil { return nil, err } - - err := validateDataDirAccess(path) - if err != nil { + if exists, err := dataFileExists(path); err != nil { return nil, err + } else if exists { + return nil, fmt.Errorf("data file exists we have to index it") } lockFile, err := createFlock(path) diff --git a/path.go b/path.go new file mode 100644 index 0000000..86dccf7 --- /dev/null +++ b/path.go @@ -0,0 +1,100 @@ +package storage + +import ( + "fmt" + "os" + "path/filepath" +) + +const ( + dataFileFormatSuffix = ".dat" +) + +func validatePathFormat(path string) error { + if path == "" || path[len(path)-1] != '/' { + return fmt.Errorf("path is mandatory and should end with a /") + } + return nil +} + +func ensureDataDirectoryExists(path string) error { + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(path, 0o755); err != nil { + return err + } else { + return nil + } + } else { + return err + } + } + if !stat.IsDir() { + return fmt.Errorf("path is not a directory") + } + return nil +} + +func validateWriteAccess(path string) error { + testPath := filepath.Join(path, "test-access-file") + testFile, err := os.OpenFile(testPath, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return err + } + + _, err = testFile.WriteString("test") + if err != nil { + return err + } + + err = testFile.Close() + if err != nil { + return err + } + + err = os.Remove(testPath) + if err != nil { + return err + } + + return nil +} + +func validateDataPath(path string) error { + if err := validatePathFormat(path); err != nil { + return err + } + + if err := ensureDataDirectoryExists(path); err != nil { + return err + } + + if err := validateWriteAccess(path); err != nil { + return err + } + + return nil +} + +func dataFileExists(path string) (bool, error) { + exists := false + err := filepath.WalkDir(path, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if info, err := d.Info(); err != nil { + return err + } else { + if info.Size() > 0 && filepath.Ext(path) == dataFileFormatSuffix { + exists = true + return filepath.SkipDir + } + } + return nil + }) + return exists, err +} diff --git a/path_test.go b/path_test.go new file mode 100644 index 0000000..c849c0d --- /dev/null +++ b/path_test.go @@ -0,0 +1,91 @@ +package storage + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidatePathFormat(t *testing.T) { + tests := []struct { + path string + hasErr bool + }{ + {"", true}, + {"path", true}, + {"path/", false}, + } + + for _, test := range tests { + err := validatePathFormat(test.path) + if test.hasErr { + assert.Error(t, err, "Expected an error for path '%s'", test.path) + } else { + assert.NoError(t, err, "Expected no error for path '%s'", test.path) + } + } +} + +func TestEnsureDataDirectoryExists(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_storage") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + path := filepath.Join(tempDir, "data/") + err = ensureDataDirectoryExists(path) + require.NoError(t, err, "Failed to ensure directory exists: %v", err) + + _, err = os.Stat(path) + require.NoError(t, err, "Directory was not created: %v", err) + + assert.True(t, isDir(path), "Path is not a directory") +} + +func TestValidateWriteAccess(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_storage") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + err = validateWriteAccess(tempDir + "/") + assert.NoError(t, err, "Failed to test write access: %v", err) +} + +func TestDataFileExists(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_storage") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + dataFilePath := filepath.Join(tempDir, "data"+dataFileFormatSuffix) + dataFile, err := os.OpenFile(dataFilePath, os.O_CREATE|os.O_WRONLY, 0o644) + _, err = dataFile.Write([]byte("test")) + require.NoError(t, err, "Failed to write to test .dat file: %v", err) + + exists, err := dataFileExists(tempDir) + require.NoError(t, err, "Failed to check if data file exists: %v", err) + assert.True(t, exists, "Expected data file to exist") +} + +func TestDataFileNotExists(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_storage") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + dataFilePath := filepath.Join(tempDir, "data"+dataFileFormatSuffix) + _, err = os.Create(dataFilePath) + require.NoError(t, err, "Failed to create test .dat file: %v", err) + + exists, err := dataFileExists(tempDir) + require.NoError(t, err, "Failed to check if data file exists: %v", err) + assert.False(t, exists, "Expected data file to exist") +} + +func isDir(path string) bool { + stat, err := os.Stat(path) + if err != nil { + return false + } + return stat.IsDir() +} diff --git a/unix.go b/unix.go index 6612137..88cde0c 100644 --- a/unix.go +++ b/unix.go @@ -6,8 +6,7 @@ import ( ) const ( - lockFileName = ".lock" - testAccessFileName = "test-access-file" + lockFileName = ".lock" ) func createFlock(path string) (*os.File, error) { @@ -23,19 +22,3 @@ func createFlock(path string) (*os.File, error) { return lockFile, nil } - -func validateDataDirAccess(path string) error { - testFile, err := os.OpenFile(path+testAccessFileName, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - err = testFile.Close() - if err != nil { - return err - } - err = os.Remove(path + testAccessFileName) - if err != nil { - return err - } - return nil -}