From a0c52d42c122db557321df8bc1be9028b25dba4a Mon Sep 17 00:00:00 2001 From: Tigran Date: Sat, 25 Jun 2022 15:34:24 +0400 Subject: [PATCH] rename mem fs with descendants --- memmap.go | 68 ++++++++++++++++++++++++++++++++------- memmap_test.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 11 deletions(-) diff --git a/memmap.go b/memmap.go index ea0798d8..8b83d000 100644 --- a/memmap.go +++ b/memmap.go @@ -87,6 +87,18 @@ func (m *MemMapFs) findParent(f *mem.FileData) *mem.FileData { return pfile } +func (m *MemMapFs) findDescendants(name string) []*mem.FileData { + fData := m.getData() + descendants := make([]*mem.FileData, 0, len(fData)) + for p, dFile := range fData { + if strings.HasPrefix(p, name+FilePathSeparator) { + descendants = append(descendants, dFile) + } + } + + return descendants +} + func (m *MemMapFs) registerWithParent(f *mem.FileData, perm os.FileMode) { if f == nil { return @@ -290,33 +302,67 @@ func (m *MemMapFs) RemoveAll(path string) error { return nil } -func (m *MemMapFs) Rename(oldname, newname string) error { - oldname = normalizePath(oldname) - newname = normalizePath(newname) +func (m *MemMapFs) Rename(oldName, newName string) error { + oldName = normalizePath(oldName) + newName = normalizePath(newName) - if oldname == newname { + if oldName == newName { return nil } m.mu.RLock() defer m.mu.RUnlock() - if _, ok := m.getData()[oldname]; ok { + if _, ok := m.getData()[oldName]; ok { m.mu.RUnlock() m.mu.Lock() - m.unRegisterWithParent(oldname) - fileData := m.getData()[oldname] - delete(m.getData(), oldname) - mem.ChangeFileName(fileData, newname) - m.getData()[newname] = fileData + err := m.unRegisterWithParent(oldName) + if err != nil { + return err + } + + fileData := m.getData()[oldName] + mem.ChangeFileName(fileData, newName) + m.getData()[newName] = fileData + + err = m.renameDescendants(oldName, newName) + if err != nil { + return err + } + + delete(m.getData(), oldName) + m.registerWithParent(fileData, 0) m.mu.Unlock() m.mu.RLock() } else { - return &os.PathError{Op: "rename", Path: oldname, Err: ErrFileNotFound} + return &os.PathError{Op: "rename", Path: oldName, Err: ErrFileNotFound} } return nil } +func (m *MemMapFs) renameDescendants(oldName, newName string) error { + descendants := m.findDescendants(oldName) + removes := make([]string, 0, len(descendants)) + for _, desc := range descendants { + descNewName := strings.Replace(desc.Name(), oldName, newName, 1) + err := m.unRegisterWithParent(desc.Name()) + if err != nil { + return err + } + + removes = append(removes, desc.Name()) + mem.ChangeFileName(desc, descNewName) + m.getData()[descNewName] = desc + + m.registerWithParent(desc, 0) + } + for _, r := range removes { + delete(m.getData(), r) + } + + return nil +} + func (m *MemMapFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { fileInfo, err := m.Stat(name) return fileInfo, false, err diff --git a/memmap_test.go b/memmap_test.go index 881a61bc..486d25f0 100644 --- a/memmap_test.go +++ b/memmap_test.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" "time" ) @@ -692,3 +693,88 @@ func TestMemFsLstatIfPossible(t *testing.T) { t.Fatalf("Function indicated lstat was called. This should never be true.") } } + +func TestMemMapFsRename(t *testing.T) { + t.Parallel() + + fs := &MemMapFs{} + tDir := testDir(fs) + rFrom := "/renamefrom" + rTo := "/renameto" + rExists := "/renameexists" + + type test struct { + dirs []string + from string + to string + exists string + } + + parts := strings.Split(tDir, "/") + root := "/" + if len(parts) > 1 { + root = filepath.Join("/", parts[1]) + } + + testData := make([]test, 0, len(parts)) + + i := len(parts) + for i > 0 { + prefix := strings.Join(parts[:i], "/") + suffix := strings.Join(parts[i:], "/") + testData = append(testData, test{ + dirs: []string{ + filepath.Join(prefix, rFrom, suffix), + filepath.Join(prefix, rExists, suffix), + }, + from: filepath.Join(prefix, rFrom), + to: filepath.Join(prefix, rTo), + exists: filepath.Join(prefix, rExists), + }) + i-- + } + + for _, data := range testData { + err := fs.RemoveAll(root) + if err != nil { + t.Fatalf("%s: RemoveAll %q failed: %v", fs.Name(), root, err) + } + + for _, dir := range data.dirs { + err = fs.MkdirAll(dir, os.FileMode(0775)) + if err != nil { + t.Fatalf("%s: MkdirAll %q failed: %v", fs.Name(), dir, err) + } + } + + dataCnt := len(fs.getData()) + err = fs.Rename(data.from, data.to) + if err != nil { + t.Fatalf("%s: rename %q, %q failed: %v", fs.Name(), data.from, data.to, err) + } + err = fs.Mkdir(data.from, os.FileMode(0775)) + if err != nil { + t.Fatalf("%s: Mkdir %q failed: %v", fs.Name(), data.from, err) + } + + err = fs.Rename(data.from, data.exists) + if err != nil { + t.Errorf("%s: rename %q, %q failed: %v", fs.Name(), data.from, data.exists, err) + } + + for p, _ := range fs.getData() { + if strings.Contains(p, data.from) { + t.Errorf("File was not renamed to renameto: %v", p) + } + } + + _, err = fs.Stat(data.to) + if err != nil { + t.Errorf("%s: stat %q failed: %v", fs.Name(), data.to, err) + } + + if dataCnt != len(fs.getData()) { + t.Errorf("invalid data len: expected %v, get %v", dataCnt, len(fs.getData())) + } + } +}