Chris Lu
11 years ago
10 changed files with 536 additions and 148 deletions
-
26go/filer/design.txt
-
18go/filer/directory.go
-
232go/filer/directory_in_map.go
-
73go/filer/directory_test.go
-
19go/filer/filer.go
-
64go/filer/filer_embedded.go
-
70go/filer/filer_in_leveldb.go
-
14go/util/file_util.go
-
144go/weed/weed_server/filer_server.go
-
24go/weed/weed_server/filer_server_handlers.go
@ -0,0 +1,26 @@ |
|||||
|
Design Assumptions: |
||||
|
1. the number of directories are magnitudely smaller than the number of files |
||||
|
2. unlimited number of files under any directories |
||||
|
Phylosophy: |
||||
|
metadata for directories and files should be separated |
||||
|
Design: |
||||
|
Store directories in normal map |
||||
|
all of directories hopefully all be in memory |
||||
|
efficient to move/rename/list_directories |
||||
|
Log directory changes to append only log file |
||||
|
Store files in sorted string table in <dir_id/filename> format |
||||
|
efficient to list_files, just simple iterator |
||||
|
efficient to locate files, binary search |
||||
|
|
||||
|
Testing: |
||||
|
1. starting server, "weed server -filer=true" |
||||
|
2. posting files to different folders |
||||
|
curl -F "filename=@design.txt" "http://localhost:8888/sources/" |
||||
|
curl -F "filename=@design.txt" "http://localhost:8888/design/" |
||||
|
curl -F "filename=@directory.go" "http://localhost:8888/sources/weed/go/" |
||||
|
curl -F "filename=@directory.go" "http://localhost:8888/sources/testing/go/" |
||||
|
curl -F "filename=@filer.go" "http://localhost:8888/sources/weed/go/" |
||||
|
curl -F "filename=@filer_in_leveldb.go" "http://localhost:8888/sources/weed/go/" |
||||
|
curl "http://localhost:8888/?pretty=y" |
||||
|
curl "http://localhost:8888/sources/weed/go/?pretty=y" |
||||
|
curl "http://localhost:8888/sources/weed/go/?pretty=y" |
@ -0,0 +1,18 @@ |
|||||
|
package filer |
||||
|
|
||||
|
import () |
||||
|
|
||||
|
type DirectoryId int32 |
||||
|
|
||||
|
type DirectoryEntry struct { |
||||
|
Name string //dir name without path
|
||||
|
Id DirectoryId |
||||
|
} |
||||
|
|
||||
|
type DirectoryManager interface { |
||||
|
FindDirectory(dirPath string) (DirectoryId, error) |
||||
|
ListDirectories(dirPath string) (dirNames []DirectoryEntry, err error) |
||||
|
MakeDirectory(currentDirPath string, dirName string) (DirectoryId, error) |
||||
|
MoveUnderDirectory(oldDirPath string, newParentDirPath string) error |
||||
|
DeleteDirectory(dirPath string) error |
||||
|
} |
@ -0,0 +1,232 @@ |
|||||
|
package filer |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"code.google.com/p/weed-fs/go/util" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
"strconv" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
type DirectoryEntryInMap struct { |
||||
|
Name string |
||||
|
Parent *DirectoryEntryInMap |
||||
|
SubDirectories map[string]*DirectoryEntryInMap |
||||
|
Id DirectoryId |
||||
|
} |
||||
|
|
||||
|
type DirectoryManagerInMap struct { |
||||
|
Root *DirectoryEntryInMap |
||||
|
max DirectoryId |
||||
|
logFile *os.File |
||||
|
isLoading bool |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) NewDirectoryEntryInMap(parent *DirectoryEntryInMap, name string) (d *DirectoryEntryInMap) { |
||||
|
d = &DirectoryEntryInMap{Name: name, Parent: parent} |
||||
|
d.SubDirectories = make(map[string]*DirectoryEntryInMap) |
||||
|
d.Id = dm.max |
||||
|
dm.max++ |
||||
|
parts := make([]string, 0) |
||||
|
for p := d; p != nil && p.Name != ""; p = p.Parent { |
||||
|
parts = append(parts, p.Name) |
||||
|
} |
||||
|
n := len(parts) |
||||
|
if n <= 0 { |
||||
|
return d |
||||
|
} |
||||
|
for i := 0; i < n/2; i++ { |
||||
|
parts[i], parts[n-1-i] = parts[n-1-i], parts[i] |
||||
|
} |
||||
|
dm.log("add", "/"+strings.Join(parts, "/"), strconv.Itoa(int(d.Id))) |
||||
|
return d |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) log(words ...string) { |
||||
|
if !dm.isLoading { |
||||
|
dm.logFile.WriteString(strings.Join(words, "\t") + "\n") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func NewDirectoryManagerInMap(dirLogFile string) (dm *DirectoryManagerInMap, err error) { |
||||
|
dm = &DirectoryManagerInMap{} |
||||
|
dm.Root = dm.NewDirectoryEntryInMap(nil, "") |
||||
|
if dm.logFile, err = os.OpenFile(dirLogFile, os.O_RDWR|os.O_CREATE, 0644); err != nil { |
||||
|
return nil, fmt.Errorf("cannot write directory log file %s.idx: %s", dirLogFile, err.Error()) |
||||
|
} |
||||
|
return dm, dm.load() |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) processEachLine(line string) error { |
||||
|
if strings.HasPrefix(line, "#") { |
||||
|
return nil |
||||
|
} |
||||
|
if line == "" { |
||||
|
return nil |
||||
|
} |
||||
|
parts := strings.Split(line, "\t") |
||||
|
if len(parts) == 0 { |
||||
|
return nil |
||||
|
} |
||||
|
switch parts[0] { |
||||
|
case "add": |
||||
|
v, pe := strconv.Atoi(parts[2]) |
||||
|
if pe != nil { |
||||
|
return pe |
||||
|
} |
||||
|
if e := dm.loadDirectory(parts[1], DirectoryId(v)); e != nil { |
||||
|
return e |
||||
|
} |
||||
|
case "mov": |
||||
|
if e := dm.MoveUnderDirectory(parts[1], parts[2]); e != nil { |
||||
|
return e |
||||
|
} |
||||
|
case "del": |
||||
|
if e := dm.DeleteDirectory(parts[1]); e != nil { |
||||
|
return e |
||||
|
} |
||||
|
default: |
||||
|
fmt.Printf("line %s has %s!\n", line, parts[0]) |
||||
|
return nil |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func (dm *DirectoryManagerInMap) load() error { |
||||
|
dm.max = 0 |
||||
|
lines := bufio.NewReader(dm.logFile) |
||||
|
dm.isLoading = true |
||||
|
defer func() { dm.isLoading = false }() |
||||
|
for { |
||||
|
line, err := util.Readln(lines) |
||||
|
if err != nil && err != io.EOF { |
||||
|
return err |
||||
|
} |
||||
|
if pe := dm.processEachLine(string(line)); pe != nil { |
||||
|
return pe |
||||
|
} |
||||
|
if err == io.EOF { |
||||
|
return nil |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) findDirectory(dirPath string) (*DirectoryEntryInMap, error) { |
||||
|
if dirPath == "" { |
||||
|
return dm.Root, nil |
||||
|
} |
||||
|
dirPath = filepath.Clean(dirPath) |
||||
|
if dirPath == "/" { |
||||
|
return dm.Root, nil |
||||
|
} |
||||
|
parts := strings.Split(dirPath, "/") |
||||
|
dir := dm.Root |
||||
|
for i := 1; i < len(parts); i++ { |
||||
|
if sub, ok := dir.SubDirectories[parts[i]]; ok { |
||||
|
dir = sub |
||||
|
} else { |
||||
|
return dm.Root, fmt.Errorf("Directory %s Not Found", dirPath) |
||||
|
} |
||||
|
} |
||||
|
return dir, nil |
||||
|
} |
||||
|
func (dm *DirectoryManagerInMap) FindDirectory(dirPath string) (DirectoryId, error) { |
||||
|
d, e := dm.findDirectory(dirPath) |
||||
|
if e == nil { |
||||
|
return d.Id, nil |
||||
|
} |
||||
|
return dm.Root.Id, e |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) loadDirectory(dirPath string, dirId DirectoryId) error { |
||||
|
dirPath = filepath.Clean(dirPath) |
||||
|
if dirPath == "/" { |
||||
|
return nil |
||||
|
} |
||||
|
parts := strings.Split(dirPath, "/") |
||||
|
dir := dm.Root |
||||
|
for i := 1; i < len(parts); i++ { |
||||
|
sub, ok := dir.SubDirectories[parts[i]] |
||||
|
if !ok { |
||||
|
if i != len(parts)-1 { |
||||
|
return fmt.Errorf("%s should be created after parent %s!", dirPath, parts[i]) |
||||
|
} |
||||
|
sub = dm.NewDirectoryEntryInMap(dir, parts[i]) |
||||
|
if sub.Id != dirId { |
||||
|
return fmt.Errorf("%s should be have id %v instead of %v!", dirPath, sub.Id, dirId) |
||||
|
} |
||||
|
dir.SubDirectories[parts[i]] = sub |
||||
|
} |
||||
|
dir = sub |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) makeDirectory(dirPath string) (dir *DirectoryEntryInMap, created bool) { |
||||
|
dirPath = filepath.Clean(dirPath) |
||||
|
if dirPath == "/" { |
||||
|
return dm.Root, false |
||||
|
} |
||||
|
parts := strings.Split(dirPath, "/") |
||||
|
dir = dm.Root |
||||
|
for i := 1; i < len(parts); i++ { |
||||
|
sub, ok := dir.SubDirectories[parts[i]] |
||||
|
if !ok { |
||||
|
sub = dm.NewDirectoryEntryInMap(dir, parts[i]) |
||||
|
dir.SubDirectories[parts[i]] = sub |
||||
|
created = true |
||||
|
} |
||||
|
dir = sub |
||||
|
} |
||||
|
return dir, created |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) MakeDirectory(dirPath string) (DirectoryId, error) { |
||||
|
dir, _ := dm.makeDirectory(dirPath) |
||||
|
return dir.Id, nil |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) MoveUnderDirectory(oldDirPath string, newParentDirPath string) error { |
||||
|
oldDir, oe := dm.findDirectory(oldDirPath) |
||||
|
if oe != nil { |
||||
|
return oe |
||||
|
} |
||||
|
parentDir, pe := dm.findDirectory(newParentDirPath) |
||||
|
if pe != nil { |
||||
|
return pe |
||||
|
} |
||||
|
delete(oldDir.Parent.SubDirectories, oldDir.Name) |
||||
|
parentDir.SubDirectories[oldDir.Name] = oldDir |
||||
|
oldDir.Parent = parentDir |
||||
|
dm.log("mov", oldDirPath, newParentDirPath) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (dm *DirectoryManagerInMap) ListDirectories(dirPath string) (dirNames []DirectoryEntry, err error) { |
||||
|
d, e := dm.findDirectory(dirPath) |
||||
|
if e != nil { |
||||
|
return dirNames, e |
||||
|
} |
||||
|
for k, v := range d.SubDirectories { |
||||
|
dirNames = append(dirNames, DirectoryEntry{Name: k, Id: v.Id}) |
||||
|
} |
||||
|
return dirNames, nil |
||||
|
} |
||||
|
func (dm *DirectoryManagerInMap) DeleteDirectory(dirPath string) error { |
||||
|
if dirPath == "/" { |
||||
|
return fmt.Errorf("Can not delete %s", dirPath) |
||||
|
} |
||||
|
d, e := dm.findDirectory(dirPath) |
||||
|
if e != nil { |
||||
|
return e |
||||
|
} |
||||
|
if len(d.SubDirectories) != 0 { |
||||
|
return fmt.Errorf("dir %s still has sub directories", dirPath) |
||||
|
} |
||||
|
delete(d.Parent.SubDirectories, d.Name) |
||||
|
d.Parent = nil |
||||
|
dm.log("del", dirPath) |
||||
|
return nil |
||||
|
} |
@ -0,0 +1,73 @@ |
|||||
|
package filer |
||||
|
|
||||
|
import ( |
||||
|
"os" |
||||
|
"strings" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func TestDirectory(t *testing.T) { |
||||
|
{ |
||||
|
dm, _ := NewDirectoryManagerInMap("/tmp/dir.log") |
||||
|
dm.MakeDirectory("/a/b/c") |
||||
|
dm.MakeDirectory("/a/b/d") |
||||
|
dm.MakeDirectory("/a/b/e") |
||||
|
dm.MakeDirectory("/a/b/e/f") |
||||
|
dm.MakeDirectory("/a/b/e/f/g") |
||||
|
dm.MoveUnderDirectory("/a/b/e/f/g", "/a/b") |
||||
|
dm.MakeDirectory("/a/b/g/h/i") |
||||
|
dm.DeleteDirectory("/a/b/e/f") |
||||
|
dm.DeleteDirectory("/a/b/e") |
||||
|
dirNames, _ := dm.ListDirectories("/a/b/e") |
||||
|
for _, v := range dirNames { |
||||
|
println("sub1 dir:", v.Name, "id", v.Id) |
||||
|
} |
||||
|
dm.logFile.Close() |
||||
|
|
||||
|
var path []string |
||||
|
printTree(dm.Root, path) |
||||
|
|
||||
|
dm2, e := NewDirectoryManagerInMap("/tmp/dir.log") |
||||
|
if e != nil { |
||||
|
println("load error", e.Error()) |
||||
|
} |
||||
|
if !compare(dm.Root, dm2.Root) { |
||||
|
t.Fatal("restored dir not the same!") |
||||
|
} |
||||
|
printTree(dm2.Root, path) |
||||
|
} |
||||
|
if true { |
||||
|
os.Remove("/tmp/dir.log") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func printTree(node *DirectoryEntryInMap, path []string) { |
||||
|
println(strings.Join(path, "/") + "/" + node.Name) |
||||
|
path = append(path, node.Name) |
||||
|
for _, v := range node.SubDirectories { |
||||
|
printTree(v, path) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func compare(root1 *DirectoryEntryInMap, root2 *DirectoryEntryInMap) bool { |
||||
|
if len(root1.SubDirectories) != len(root2.SubDirectories) { |
||||
|
return false |
||||
|
} |
||||
|
if root1.Name != root2.Name { |
||||
|
return false |
||||
|
} |
||||
|
if root1.Id != root2.Id { |
||||
|
return false |
||||
|
} |
||||
|
if !(root1.Parent == nil && root2.Parent == nil) { |
||||
|
if root1.Parent.Id != root2.Parent.Id { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
for k, v := range root1.SubDirectories { |
||||
|
if !compare(v, root2.SubDirectories[k]) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package filer |
||||
|
|
||||
|
import () |
||||
|
|
||||
|
type FileId string //file id on weedfs
|
||||
|
|
||||
|
type FileEntry struct { |
||||
|
Name string //file name without path
|
||||
|
Id FileId |
||||
|
} |
||||
|
|
||||
|
type Filer interface { |
||||
|
CreateFile(filePath string, fid string) (err error) |
||||
|
FindFile(filePath string) (fid string, err error) |
||||
|
ListDirectories(dirPath string) (dirs []DirectoryEntry, err error) |
||||
|
ListFiles(dirPath string, lastFileName string, limit int) (files []FileEntry, err error) |
||||
|
DeleteDirectory(dirPath string) (err error) |
||||
|
DeleteFile(filePath string) (fid string, err error) |
||||
|
} |
@ -0,0 +1,64 @@ |
|||||
|
package filer |
||||
|
|
||||
|
import ( |
||||
|
"path/filepath" |
||||
|
) |
||||
|
|
||||
|
type FilerEmbedded struct { |
||||
|
directories *DirectoryManagerInMap |
||||
|
files *FileListInLevelDb |
||||
|
} |
||||
|
|
||||
|
func NewFilerEmbedded(dir string) (filer *FilerEmbedded, err error) { |
||||
|
dm, de := NewDirectoryManagerInMap(filepath.Join(dir, "dir.log")) |
||||
|
if de != nil { |
||||
|
return nil, de |
||||
|
} |
||||
|
fl, fe := NewFileListInLevelDb(dir) |
||||
|
if fe != nil { |
||||
|
return nil, fe |
||||
|
} |
||||
|
filer = &FilerEmbedded{ |
||||
|
directories: dm, |
||||
|
files: fl, |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (filer *FilerEmbedded) CreateFile(filePath string, fid string) (err error) { |
||||
|
dir, file := filepath.Split(filePath) |
||||
|
dirId, e := filer.directories.MakeDirectory(dir) |
||||
|
if e != nil { |
||||
|
return e |
||||
|
} |
||||
|
return filer.files.CreateFile(dirId, file, fid) |
||||
|
} |
||||
|
func (filer *FilerEmbedded) FindFile(filePath string) (fid string, err error) { |
||||
|
dir, file := filepath.Split(filePath) |
||||
|
dirId, e := filer.directories.FindDirectory(dir) |
||||
|
if e != nil { |
||||
|
return "", e |
||||
|
} |
||||
|
return filer.files.FindFile(dirId, file) |
||||
|
} |
||||
|
func (filer *FilerEmbedded) ListDirectories(dirPath string) (dirs []DirectoryEntry, err error) { |
||||
|
return filer.directories.ListDirectories(dirPath) |
||||
|
} |
||||
|
func (filer *FilerEmbedded) ListFiles(dirPath string, lastFileName string, limit int) (files []FileEntry, err error) { |
||||
|
dirId, e := filer.directories.FindDirectory(dirPath) |
||||
|
if e != nil { |
||||
|
return nil, e |
||||
|
} |
||||
|
return filer.files.ListFiles(dirId, lastFileName, limit), nil |
||||
|
} |
||||
|
func (filer *FilerEmbedded) DeleteDirectory(dirPath string) (err error) { |
||||
|
return filer.directories.DeleteDirectory(dirPath) |
||||
|
} |
||||
|
func (filer *FilerEmbedded) DeleteFile(filePath string) (fid string, err error) { |
||||
|
dir, file := filepath.Split(filePath) |
||||
|
dirId, e := filer.directories.FindDirectory(dir) |
||||
|
if e != nil { |
||||
|
return "", e |
||||
|
} |
||||
|
return filer.files.DeleteFile(dirId, file) |
||||
|
} |
@ -0,0 +1,70 @@ |
|||||
|
package filer |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"code.google.com/p/weed-fs/go/glog" |
||||
|
"github.com/syndtr/goleveldb/leveldb" |
||||
|
"github.com/syndtr/goleveldb/leveldb/util" |
||||
|
) |
||||
|
|
||||
|
type FileListInLevelDb struct { |
||||
|
db *leveldb.DB |
||||
|
} |
||||
|
|
||||
|
func NewFileListInLevelDb(dir string) (fl *FileListInLevelDb, err error) { |
||||
|
fl = &FileListInLevelDb{} |
||||
|
if fl.db, err = leveldb.OpenFile(dir, nil); err != nil { |
||||
|
return |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func genKey(dirId DirectoryId, fileName string) []byte { |
||||
|
ret := make([]byte, 0, 4+len(fileName)) |
||||
|
for i := 3; i >= 0; i-- { |
||||
|
ret = append(ret, byte(dirId>>(uint(i)*8))) |
||||
|
} |
||||
|
ret = append(ret, []byte(fileName)...) |
||||
|
return ret |
||||
|
} |
||||
|
|
||||
|
func (fl *FileListInLevelDb) CreateFile(dirId DirectoryId, fileName string, fid string) (err error) { |
||||
|
glog.V(4).Infoln("directory", dirId, "fileName", fileName, "fid", fid) |
||||
|
return fl.db.Put(genKey(dirId, fileName), []byte(fid), nil) |
||||
|
} |
||||
|
func (fl *FileListInLevelDb) DeleteFile(dirId DirectoryId, fileName string) (fid string, err error) { |
||||
|
if fid, err = fl.FindFile(dirId, fileName); err != nil { |
||||
|
return |
||||
|
} |
||||
|
err = fl.db.Delete(genKey(dirId, fileName), nil) |
||||
|
return fid, err |
||||
|
} |
||||
|
func (fl *FileListInLevelDb) FindFile(dirId DirectoryId, fileName string) (fid string, err error) { |
||||
|
data, e := fl.db.Get(genKey(dirId, fileName), nil) |
||||
|
if e != nil { |
||||
|
return "", e |
||||
|
} |
||||
|
return string(data), nil |
||||
|
} |
||||
|
func (fl *FileListInLevelDb) ListFiles(dirId DirectoryId, lastFileName string, limit int) (files []FileEntry) { |
||||
|
glog.V(4).Infoln("directory", dirId, "lastFileName", lastFileName, "limit", limit) |
||||
|
dirKey := genKey(dirId, "") |
||||
|
iter := fl.db.NewIterator(&util.Range{Start: genKey(dirId, lastFileName)}, nil) |
||||
|
limitCounter := -1 |
||||
|
for iter.Next() { |
||||
|
limitCounter++ |
||||
|
if limit > 0 { |
||||
|
if limitCounter > limit { |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
key := iter.Key() |
||||
|
if !bytes.HasPrefix(key, dirKey) { |
||||
|
break |
||||
|
} |
||||
|
fileName := key[len(dirKey):] |
||||
|
files = append(files, FileEntry{Name: string(fileName), Id: FileId(string(iter.Value()))}) |
||||
|
} |
||||
|
iter.Release() |
||||
|
return |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue