func (vfs FuseVfs) Unlink(name string, context *fuse.Context) fuse.Status { log.Infof("BEGIN Unlink(%v)", name) defer log.Infof("END Unlink(%v)", name) fileId, err := vfs.parseFileId(name) if err != nil { log.Fatal("Could not unlink: ", err) } if fileId == 0 { // cannot unlink tag directories return fuse.EPERM } path := vfs.splitPath(name) tagNames := path[1 : len(path)-1] for _, tagName := range tagNames { tag, err := vfs.store.Db.TagByName(tagName) if err != nil { log.Fatal(err) } if tag == nil { log.Fatalf("Could not retrieve tag '%v'.", tagName) } err = vfs.store.RemoveFileTag(fileId, tag.Id) if err != nil { log.Fatal(err) } } return fuse.OK }
func (vfs FuseVfs) openTaggedEntryDir(path []string) ([]fuse.DirEntry, fuse.Status) { log.Infof("BEGIN openTaggedEntryDir(%v)", path) defer log.Infof("END openTaggedEntryDir(%v)", path) tagIds, err := vfs.tagNamesToIds(path) if err != nil { log.Fatalf("Could not lookup tag IDs: %v.", err) } if tagIds == nil { return nil, fuse.ENOENT } furtherTagIds, err := vfs.store.TagsForTags(tagIds) if err != nil { log.Fatalf("Could not retrieve tags for tags: %v", err) } files, err := vfs.store.FilesWithTags(tagIds, []uint{}) if err != nil { log.Fatalf("Could not retrieve tagged files: %v", err) } entries := make([]fuse.DirEntry, 0, len(files)+len(furtherTagIds)) for _, tag := range furtherTagIds { entries = append(entries, fuse.DirEntry{Name: tag.Name, Mode: fuse.S_IFDIR | 0755}) } for _, file := range files { linkName := vfs.getLinkName(file) entries = append(entries, fuse.DirEntry{Name: linkName, Mode: fuse.S_IFLNK}) } return entries, fuse.OK }
func (vfs FuseVfs) topDirectories() ([]fuse.DirEntry, fuse.Status) { log.Infof("BEGIN topDirectories") defer log.Infof("END topDirectories") entries := make([]fuse.DirEntry, 0, 1) entries = append(entries, fuse.DirEntry{Name: "tags", Mode: fuse.S_IFDIR}) return entries, fuse.OK }
func (vfs FuseVfs) getTaggedEntryAttr(path []string) (*fuse.Attr, fuse.Status) { log.Infof("BEGIN getTaggedEntryAttr(%v)", path) defer log.Infof("END getTaggedEntryAttr(%v)", path) pathLength := len(path) name := path[pathLength-1] fileId, err := vfs.parseFileId(name) if err != nil { return nil, fuse.ENOENT } if fileId == 0 { // tag directory tagIds, err := vfs.tagNamesToIds(path) if err != nil { log.Fatalf("Could not lookup tag IDs: %v.", err) } if tagIds == nil { return nil, fuse.ENOENT } //TODO slow // fileCount, err := vfs.store.FileCountWithTags(tagIds) // if err != nil { // log.Fatalf("Could not retrieve count of files with tags: %v. (%v)", path, err) // } fileCount := 0 now := time.Now() return &fuse.Attr{Mode: fuse.S_IFDIR | 0755, Nlink: 2, Size: uint64(fileCount), Mtime: uint64(now.Unix()), Mtimensec: uint32(now.Nanosecond())}, fuse.OK } file, err := vfs.store.File(fileId) if err != nil { log.Fatalf("Could not retrieve file #%v: %v", fileId, err) } if file == nil { return &fuse.Attr{Mode: fuse.S_IFREG}, fuse.ENOENT } fileInfo, err := os.Stat(file.Path()) var size int64 var modTime time.Time if err == nil { size = fileInfo.Size() modTime = fileInfo.ModTime() } else { size = 0 modTime = time.Time{} } return &fuse.Attr{Mode: fuse.S_IFLNK | 0755, Size: uint64(size), Mtime: uint64(modTime.Unix()), Mtimensec: uint32(modTime.Nanosecond())}, fuse.OK }
func (vfs FuseVfs) getTagsAttr() (*fuse.Attr, fuse.Status) { log.Infof("BEGIN getTagsAttr") defer log.Infof("END getTagsAttr") tagCount, err := vfs.store.Db.TagCount() if err != nil { log.Fatalf("Could not get tag count: %v", err) } now := time.Now() return &fuse.Attr{Mode: fuse.S_IFDIR | 0755, Nlink: 2, Size: uint64(tagCount), Mtime: uint64(now.Unix()), Mtimensec: uint32(now.Nanosecond())}, fuse.OK }
func (command UntagCommand) untagPath(store *storage.Storage, path string, tagIds []uint) error { absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("%v: could not get absolute path: %v", path, err) } file, err := store.FileByPath(absPath) if err != nil { return fmt.Errorf("%v: could not retrieve file: %v", path, err) } if file == nil { return fmt.Errorf("%v: file is not tagged.", path) } for _, tagId := range tagIds { if command.verbose { log.Infof("%v: unapplying tag #%v.", file.Path(), tagId) } if err := store.RemoveFileTag(file.Id, tagId); err != nil { return fmt.Errorf("%v: could not remove tag #%v: %v", file.Path(), tagId, err) } } if err := command.removeUntaggedFile(store, file); err != nil { return err } if command.recursive { childFiles, err := store.FilesByDirectory(file.Path()) if err != nil { return fmt.Errorf("%v: could not retrieve files for directory: %v", file.Path()) } for _, childFile := range childFiles { for _, tagId := range tagIds { if command.verbose { log.Infof("%v: unapplying tag #%v.", childFile.Path(), tagId) } if err := store.RemoveFileTag(childFile.Id, tagId); err != nil { return fmt.Errorf("%v: could not remove tag #%v: %v", childFile.Path(), tagId, err) } } if err := command.removeUntaggedFile(store, childFile); err != nil { return err } } } return nil }
func (vfs FuseVfs) Readlink(name string, context *fuse.Context) (string, fuse.Status) { log.Infof("BEGIN Readlink(%v)", name) defer log.Infof("END Readlink(%v)", name) path := vfs.splitPath(name) switch path[0] { case "tags": return vfs.readTaggedEntryLink(path[1:]) } return "", fuse.ENOENT }
func (command RepairCommand) repairModified(store *storage.Storage, modified fileIdAndInfoMap) error { if command.verbose { log.Info("repairing modified files") } for path, fileIdAndStat := range modified { fileId := fileIdAndStat.fileId stat := fileIdAndStat.stat log.Infof("%v: modified", path) fingerprint, err := fingerprint.Create(path) if err != nil { return fmt.Errorf("%v: could not create fingerprint: %v", path, err) } if !command.pretend { _, err := store.UpdateFile(fileId, path, fingerprint, stat.ModTime(), stat.Size(), stat.IsDir()) if err != nil { return fmt.Errorf("%v: could not update file in database: %v", path, err) } } } return nil }
func (command DupesCommand) findDuplicatesInDb() error { store, err := storage.Open() if err != nil { return fmt.Errorf("could not open storage: %v", err) } defer store.Close() if command.verbose { log.Info("identifying duplicate files.") } fileSets, err := store.DuplicateFiles() if err != nil { return fmt.Errorf("could not identify duplicate files: %v", err) } if command.verbose { log.Infof("found %v sets of duplicate files.", len(fileSets)) } for index, fileSet := range fileSets { if index > 0 { log.Print() } log.Printf("Set of %v duplicates:", len(fileSet)) for _, file := range fileSet { relPath := _path.Rel(file.Path()) log.Printf(" %v", relPath) } } return nil }
func (vfs FuseVfs) tagDirectories() ([]fuse.DirEntry, fuse.Status) { log.Infof("BEGIN tagDirectories") defer log.Infof("END tagDirectories") tags, err := vfs.store.Db.Tags() if err != nil { log.Fatalf("Could not retrieve tags: %v", err) } entries := make([]fuse.DirEntry, len(tags)) for index, tag := range tags { entries[index] = fuse.DirEntry{Name: tag.Name, Mode: fuse.S_IFDIR} } return entries, fuse.OK }
func (command TagsCommand) listTagsForPath(store *storage.Storage, path string) error { if command.verbose { log.Infof("%v: retrieving tags.", path) } var tags, err = store.TagsForPath(path) if err != nil { return fmt.Errorf("%v: could not retrieve tags: %v", path, err) } if len(tags) == 0 { _, err := os.Stat(path) if err != nil { switch { case os.IsPermission(err): log.Warnf("%v: permission denied", path) case os.IsNotExist(err): return fmt.Errorf("%v: file not found", path) default: return fmt.Errorf("%v: could not stat file: %v", path, err) } } } if command.count { log.Print(len(tags)) } else { for _, tag := range tags { log.Print(tag.Name) } } return nil }
func (command ImplyCommand) deleteImplication(store *storage.Storage, tagName, impliedTagName string) error { tag, err := store.Db.TagByName(tagName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", tagName, err) } if tag == nil { return fmt.Errorf("no such tag '%v'.", tagName) } impliedTag, err := store.Db.TagByName(impliedTagName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", impliedTagName, err) } if impliedTag == nil { return fmt.Errorf("no such tag '%v'.", impliedTagName) } if command.verbose { log.Infof("removing tag implication of '%v' to '%v'.", tagName, impliedTagName) } if err = store.RemoveImplication(tag.Id, impliedTag.Id); err != nil { return fmt.Errorf("could not add delete tag implication of '%v' to '%v': %v", tagName, impliedTagName, err) } return nil }
func (command UnmountCommand) unmount(path string) error { if command.verbose { log.Info("searching path for fusermount.") } fusermountPath, err := exec.LookPath("fusermount") if err != nil { return fmt.Errorf("could not find 'fusermount': ensure fuse is installed: %v", err) } if command.verbose { log.Infof("running: %v -u %v.", fusermountPath, path) } process, err := os.StartProcess(fusermountPath, []string{fusermountPath, "-u", path}, &os.ProcAttr{}) if err != nil { return fmt.Errorf("could not start 'fusermount': %v", err) } if command.verbose { log.Info("waiting for process to exit.") } processState, err := process.Wait() if err != nil { return fmt.Errorf("error waiting for process to exit: %v", err) } if !processState.Success() { return fmt.Errorf("could not unmount virtual filesystem.") } return nil }
func (command DupesCommand) findDuplicatesOf(paths []string) error { store, err := storage.Open() if err != nil { return fmt.Errorf("could not open storage: %v", err) } defer store.Close() first := true for _, path := range paths { if command.verbose { log.Infof("%v: identifying duplicate files.\n", path) } fp, err := fingerprint.Create(path) if err != nil { return fmt.Errorf("%v: could not create fingerprint: %v", path, err) } if fp == fingerprint.Fingerprint("") { return nil } files, err := store.FilesByFingerprint(fp) if err != nil { return fmt.Errorf("%v: could not retrieve files matching fingerprint '%v': %v", path, fp, err) } absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("%v: could not determine absolute path: %v", path, err) } // filter out the file we're searching on dupes := files.Where(func(file *database.File) bool { return file.Path() != absPath }) if len(paths) > 1 && len(dupes) > 0 { if first { first = false } else { log.Print() } log.Printf("%v duplicates of %v:", len(dupes), path) for _, dupe := range dupes { relPath := _path.Rel(dupe.Path()) log.Printf(" %v", relPath) } } else { for _, dupe := range dupes { relPath := _path.Rel(dupe.Path()) log.Print(relPath) } } } return nil }
func (command RepairCommand) repairMoved(store *storage.Storage, missing databaseFileMap, untagged fileInfoMap) error { if command.verbose { log.Info("repairing moved files") } moved := make([]string, 0, 10) for path, dbFile := range missing { if command.verbose { log.Infof("%v: searching for new location", path) } for candidatePath, stat := range untagged { if stat.Size() == dbFile.Size { fingerprint, err := fingerprint.Create(candidatePath) if err != nil { return fmt.Errorf("%v: could not create fingerprint: %v", path, err) } if fingerprint == dbFile.Fingerprint { log.Infof("%v: moved to %v", path, candidatePath) moved = append(moved, path) if !command.pretend { _, err := store.UpdateFile(dbFile.Id, candidatePath, dbFile.Fingerprint, stat.ModTime(), dbFile.Size, dbFile.IsDir) if err != nil { return fmt.Errorf("%v: could not update file in database: %v", path, err) } } delete(untagged, candidatePath) break } } } } for _, path := range moved { delete(missing, path) } return nil }
func (command RepairCommand) repairMissing(store *storage.Storage, missing databaseFileMap) error { for path, dbFile := range missing { if command.force && !command.pretend { if err := store.RemoveFileTagsByFileId(dbFile.Id); err != nil { return fmt.Errorf("%v: could not delete file-tags: %v", path, err) } if err := store.RemoveFile(dbFile.Id); err != nil { return fmt.Errorf("%v: could not delete file: %v", path, err) } log.Infof("%v: removed", path) } else { log.Infof("%v: missing", path) } } return nil }
func (command *StatusCommand) checkFile(file *database.File, report *StatusReport) error { relPath := path.Rel(file.Path()) if command.verbose { log.Infof("%v: checking file status.", file.Path()) } stat, err := os.Stat(file.Path()) if err != nil { switch { case os.IsNotExist(err): if command.verbose { log.Infof("%v: file is missing.", file.Path()) } report.AddRow(Row{relPath, MISSING}) return nil case os.IsPermission(err): log.Warnf("%v: permission denied.", file.Path()) case strings.Contains(err.Error(), "not a directory"): report.AddRow(Row{relPath, MISSING}) return nil default: return fmt.Errorf("%v: could not stat: %v", file.Path(), err) } } else { if stat.Size() != file.Size || stat.ModTime().UTC() != file.ModTime { if command.verbose { log.Infof("%v: file is modified.", file.Path()) } report.AddRow(Row{relPath, MODIFIED}) } else { if command.verbose { log.Infof("%v: file is unchanged.", file.Path()) } report.AddRow(Row{relPath, TAGGED}) } } return nil }
func (vfs FuseVfs) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { log.Infof("BEGIN OpenDir(%v)", name) defer log.Infof("END OpenDir(%v)", name) switch name { case "": return vfs.topDirectories() case "tags": return vfs.tagDirectories() } path := vfs.splitPath(name) switch path[0] { case "tags": return vfs.openTaggedEntryDir(path[1:]) } return nil, fuse.ENOENT }
func (command TagCommand) lookupTagIds(store *storage.Storage, names []string) ([]uint, error) { tags, err := store.TagsByNames(names) if err != nil { return nil, fmt.Errorf("could not retrieve tags %v: %v", names, err) } for _, name := range names { if !tags.Any(func(tag *database.Tag) bool { return tag.Name == name }) { log.Infof("New tag '%v'.", name) tag, err := store.AddTag(name) if err != nil { return nil, fmt.Errorf("could not add tag '%v': %v", name, err) } tags = append(tags, tag) } } if command.verbose { log.Infof("retrieving tag implications") } tagIds := make([]uint, len(tags)) for index, tag := range tags { tagIds[index] = tag.Id } implications, err := store.ImplicationsForTags(tagIds...) if err != nil { return nil, fmt.Errorf("could not retrieve implied tags: %v", err) } for _, implication := range implications { if !contains(tagIds, implication.ImpliedTag.Id) { log.Infof("tag '%v' is implied.", implication.ImpliedTag.Name) tagIds = append(tagIds, implication.ImpliedTag.Id) } } return tagIds, nil }
func (vfs FuseVfs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { log.Infof("BEGIN GetAttr(%v)", name) defer log.Infof("END GetAttr(%v)", name) switch name { case "": fallthrough case "tags": return vfs.getTagsAttr() } path := vfs.splitPath(name) switch path[0] { case "tags": return vfs.getTaggedEntryAttr(path[1:]) } return nil, fuse.ENOENT }
func (vfs FuseVfs) readTaggedEntryLink(path []string) (string, fuse.Status) { log.Infof("BEGIN readTaggedEntryLink(%v)", path) defer log.Infof("END readTaggedEntryLink(%v)", path) name := path[len(path)-1] fileId, err := vfs.parseFileId(name) if err != nil { log.Fatalf("Could not parse file identifier: %v", err) } if fileId == 0 { return "", fuse.ENOENT } file, err := vfs.store.File(fileId) if err != nil { log.Fatalf("Could not find file %v in database.", fileId) } return file.Path(), fuse.OK }
func (command *StatusCommand) findNewFiles(searchPath string, report *StatusReport) error { if command.verbose { log.Infof("%v: finding new files.", searchPath) } relPath := path.Rel(searchPath) if !report.ContainsRow(relPath) { report.AddRow(Row{relPath, UNTAGGED}) } absPath, err := filepath.Abs(searchPath) if err != nil { return fmt.Errorf("%v: could not get absolute path: %v", searchPath, err) } stat, err := os.Stat(absPath) if err != nil { switch { case os.IsNotExist(err): return nil case os.IsPermission(err): log.Warnf("%v: permission denied.", searchPath) return nil default: return fmt.Errorf("%v: could not stat: %v", searchPath, err) } } if !command.directory && stat.IsDir() { dir, err := os.Open(absPath) if err != nil { return fmt.Errorf("%v: could not open file: %v", searchPath, err) } dirNames, err := dir.Readdirnames(0) if err != nil { return fmt.Errorf("%v: could not read directory listing: %v", searchPath, err) } for _, dirName := range dirNames { dirPath := filepath.Join(searchPath, dirName) err = command.findNewFiles(dirPath, report) if err != nil { return err } } } return nil }
func (command StatusCommand) statusPaths(paths []string) (*StatusReport, error) { report := NewReport() store, err := storage.Open() if err != nil { return nil, fmt.Errorf("could not open storage: %v", err) } defer store.Close() for _, path := range paths { absPath, err := filepath.Abs(path) if err != nil { return nil, fmt.Errorf("%v: could not get absolute path: %v", path, err) } file, err := store.FileByPath(absPath) if err != nil { return nil, fmt.Errorf("%v: could not retrieve file: %v", path, err) } if file != nil { err = command.checkFile(file, report) if err != nil { return nil, err } } if !command.directory { if command.verbose { log.Infof("%v: retrieving files from database.", path) } files, err := store.FilesByDirectory(absPath) if err != nil { return nil, fmt.Errorf("%v: could not retrieve files for directory: %v", path, err) } err = command.checkFiles(files, report) if err != nil { return nil, err } } err = command.findNewFiles(path, report) if err != nil { return nil, err } } return report, nil }
func (command UntagCommand) removeUntaggedFile(store *storage.Storage, file *database.File) error { if command.verbose { log.Infof("%v: identifying whether file is tagged.", file.Path()) } filetagCount, err := store.FileTagCountByFileId(file.Id) if err != nil { return fmt.Errorf("%v: could not get tag count: %v", file.Path(), err) } if filetagCount == 0 { if command.verbose { log.Infof("%v: removing untagged file.", file.Path()) } err = store.RemoveFile(file.Id) if err != nil { return fmt.Errorf("%v: could not remove file: %v", file.Path(), err) } } return nil }
func (command RenameCommand) Exec(options cli.Options, args []string) error { command.verbose = options.HasOption("--verbose") store, err := storage.Open() if err != nil { return fmt.Errorf("could not open storage: %v", err) } defer store.Close() if len(args) < 2 { return fmt.Errorf("tag to rename and new name must both be specified.") } if len(args) > 2 { return fmt.Errorf("too many arguments") } sourceTagName := args[0] destTagName := args[1] sourceTag, err := store.TagByName(sourceTagName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", sourceTagName, err) } if sourceTag == nil { return fmt.Errorf("no such tag '%v'.", sourceTagName) } destTag, err := store.TagByName(destTagName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", destTagName, err) } if destTag != nil { return fmt.Errorf("tag '%v' already exists.", destTagName) } if command.verbose { log.Infof("renaming tag '%v' to '%v'.", sourceTagName, destTagName) } _, err = store.RenameTag(sourceTag.Id, destTagName) if err != nil { return fmt.Errorf("could not rename tag '%v' to '%v': %v", sourceTagName, destTagName, err) } return nil }
func (command UntagCommand) untagPathAll(store *storage.Storage, path string) error { absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("%v: could not get absolute path: %v", path, err) } file, err := store.FileByPath(absPath) if err != nil { return fmt.Errorf("%v: could not retrieve file: %v", path, err) } if file == nil { return fmt.Errorf("%v: file is not tagged.", path) } if command.verbose { log.Infof("%v: removing all tags.", file.Path()) } if err := store.RemoveFileTagsByFileId(file.Id); err != nil { return fmt.Errorf("%v: could not remove file's tags: %v", file.Path(), err) } if err := command.removeUntaggedFile(store, file); err != nil { return err } if command.recursive { childFiles, err := store.FilesByDirectory(file.Path()) if err != nil { return fmt.Errorf("%v: could not retrieve files for directory: %v", file.Path()) } for _, childFile := range childFiles { if err := store.RemoveFileTagsByFileId(childFile.Id); err != nil { return fmt.Errorf("%v: could not remove file's tags: %v", childFile.Path(), err) } if err := command.removeUntaggedFile(store, childFile); err != nil { return err } } } return nil }
func (command *TagCommand) addFile(store *storage.Storage, path string, modTime time.Time, size uint, isDir bool) (*database.File, error) { if command.verbose { log.Infof("%v: adding file.", path) } fingerprint, err := fingerprint.Create(path) if err != nil { return nil, fmt.Errorf("%v: could not create fingerprint: %v", path, err) } file, err := store.AddFile(path, fingerprint, modTime, int64(size), isDir) if err != nil { return nil, fmt.Errorf("%v: could not add file to database: %v", path, err) } return file, nil }
func (command TagCommand) tagPath(store *storage.Storage, path string, tagIds []uint) error { absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("%v: could not get absolute path: %v", path, err) } stat, err := os.Stat(path) if err != nil { switch { case os.IsPermission(err): return fmt.Errorf("%v: permisison denied", path) case os.IsNotExist(err): return fmt.Errorf("%v: no such file", path) default: return fmt.Errorf("%v: could not stat file: %v", path, err) } } file, err := store.FileByPath(absPath) if err != nil { return fmt.Errorf("%v: could not retrieve file: %v", path, err) } if file == nil { file, err = command.addFile(store, absPath, stat.ModTime(), uint(stat.Size()), stat.IsDir()) if err != nil { return fmt.Errorf("%v: could not add file: %v", path, err) } } if command.verbose { log.Infof("%v: applying tags.", file.Path()) } if err = store.AddFileTags(file.Id, tagIds); err != nil { return fmt.Errorf("%v: could not apply tags: %v", file.Path(), err) } if command.recursive && stat.IsDir() { if err = command.tagRecursively(store, path, tagIds); err != nil { return err } } return nil }
func (command TagsCommand) listTagsForPaths(store *storage.Storage, paths []string) error { for _, path := range paths { if command.verbose { log.Infof("%v: retrieving tags.", path) } var tags, err = store.TagsForPath(path) if err != nil { log.Warn(err.Error()) continue } if command.count { log.Print(path + ": " + strconv.Itoa(len(tags))) } else { log.Print(path + ": " + tagLine(tags)) } } return nil }
func (command RepairCommand) checkFiles(store *storage.Storage, paths []string) error { tree := _path.NewTree() for _, path := range paths { tree.Add(path, false) } paths = tree.TopLevel().Paths() fsPaths, err := enumerateFileSystemPaths(paths) if err != nil { return err } dbPaths, err := enumerateDatabasePaths(store, paths) if err != nil { return err } _, untagged, modified, missing := command.determineStatuses(fsPaths, dbPaths) if err = command.repairModified(store, modified); err != nil { return err } if err = command.repairMoved(store, missing, untagged); err != nil { return err } if err = command.repairMissing(store, missing); err != nil { return err } for path, _ := range untagged { log.Infof("%v: untagged", path) } //TODO cleanup: any files that have no tags: remove //TODO cleanup: any tags that do not correspond to a file: remove return nil }