func (vfs FuseVfs) getFileEntryAttr(fileId entities.FileId) (*fuse.Attr, fuse.Status) { tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() file, err := vfs.store.File(tx, 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) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status { log.Infof(2, "BEGIN Mkdir(%v)", name) defer log.Infof(2, "END Mkdir(%v)", name) path := vfs.splitPath(name) if len(path) != 2 { return fuse.EPERM } tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() switch path[0] { case tagsDir: name := path[1] if _, err := vfs.store.AddTag(tx, name); err != nil { log.Fatalf("could not create tag '%v': %v", name, err) } if err := tx.Commit(); err != nil { log.Fatalf("could not commit transaction: %v", err) } return fuse.OK case queriesDir: return fuse.EINVAL } return fuse.ENOSYS }
func (vfs FuseVfs) openTaggedEntryDir(tx *storage.Tx, path []string) ([]fuse.DirEntry, fuse.Status) { log.Infof(2, "BEGIN openTaggedEntryDir(%v)", path) defer log.Infof(2, "END openTaggedEntryDir(%v)", path) expression := pathToExpression(path) files, err := vfs.store.FilesForQuery(tx, expression, "", false, false, "name") if err != nil { log.Fatalf("could not query files: %v", err) } lastPathElement := path[len(path)-1] var valueNames []string if lastPathElement[0] != '=' { tagName := unescape(lastPathElement) valueNames, err = vfs.tagValueNamesForFiles(tx, tagName, files) if err != nil { log.Fatalf("could not retrieve values for '%v': %v", err) } } else { valueNames = []string{} } furtherTagNames, err := vfs.tagNamesForFiles(tx, files) if err != nil { log.Fatalf("could not retrieve further tags: %v", err) } entries := make([]fuse.DirEntry, 0, len(files)+len(furtherTagNames)) for _, tagName := range furtherTagNames { tagName = escape(tagName) if !containsString(path, tagName) { entries = append(entries, fuse.DirEntry{Name: tagName, Mode: fuse.S_IFDIR | 0755}) } } for _, valueName := range valueNames { valueName = escape(valueName) entries = append(entries, fuse.DirEntry{Name: "=" + valueName, 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) tagDirectories(tx *storage.Tx) ([]fuse.DirEntry, fuse.Status) { log.Infof(2, "BEGIN tagDirectories") defer log.Infof(2, "END tagDirectories") tags, err := vfs.store.Tags(tx) if err != nil { log.Fatalf("Could not retrieve tags: %v", err) } entries := make([]fuse.DirEntry, 0, len(tags)) for _, tag := range tags { if strings.ContainsAny(tag.Name, "/\\") { log.Infof(2, "Tag '%v' contains slashes so is omitted from the VFS") continue } entries = append(entries, fuse.DirEntry{Name: tag.Name, Mode: fuse.S_IFDIR}) } // show help file until there are three tags if len(tags) < 3 { entries = append(entries, fuse.DirEntry{Name: helpFilename, Mode: fuse.S_IFREG}) } return entries, fuse.OK }
func (vfs FuseVfs) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { log.Infof(2, "BEGIN OpenDir(%v)", name) defer log.Infof(2, "END OpenDir(%v)", name) tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() switch name { case "": return vfs.topFiles() case tagsDir: return vfs.tagDirectories(tx) case queriesDir: return vfs.queriesDirectories(tx) } path := vfs.splitPath(name) switch path[0] { case tagsDir: return vfs.openTaggedEntryDir(tx, path[1:]) case queriesDir: return vfs.openQueryEntryDir(tx, path[1:]) } return nil, fuse.ENOENT }
func (vfs FuseVfs) getTagsAttr() (*fuse.Attr, fuse.Status) { log.Infof(2, "BEGIN getTagsAttr") defer log.Infof(2, "END getTagsAttr") tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() tagCount, err := vfs.store.TagCount(tx) 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 (vfs FuseVfs) Rename(oldName string, newName string, context *fuse.Context) fuse.Status { log.Infof(2, "BEGIN Rename(%v, %v)", oldName, newName) defer log.Infof(2, "END Rename(%v, %v)", oldName, newName) tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() oldPath := vfs.splitPath(oldName) newPath := vfs.splitPath(newName) if len(oldPath) != 2 || len(newPath) != 2 { return fuse.EPERM } if oldPath[0] != tagsDir || newPath[0] != tagsDir { return fuse.EPERM } oldTagName := oldPath[1] newTagName := newPath[1] tag, err := vfs.store.TagByName(tx, oldTagName) if err != nil { log.Fatalf("could not retrieve tag '%v': %v", oldTagName, err) } if tag == nil { return fuse.ENOENT } if _, err := vfs.store.RenameTag(tx, tag.Id, newTagName); err != nil { log.Fatalf("could not rename tag '%v' to '%v': %v", oldTagName, newTagName, err) } if err := tx.Commit(); err != nil { log.Fatalf("could not commit transaction: %v", err) } return fuse.OK }
func (vfs FuseVfs) getTaggedEntryAttr(path []string) (*fuse.Attr, fuse.Status) { log.Infof(2, "BEGIN getTaggedEntryAttr(%v)", path) defer log.Infof(2, "END getTaggedEntryAttr(%v)", path) if len(path) == 1 && path[0] == helpFilename { now := time.Now() return &fuse.Attr{Mode: fuse.S_IFREG | 0444, Nlink: 1, Size: uint64(len(tagsDirHelp)), Mtime: uint64(now.Unix()), Mtimensec: uint32(now.Nanosecond())}, fuse.OK } name := path[len(path)-1] fileId := vfs.parseFileId(name) if fileId != 0 { return vfs.getFileEntryAttr(fileId) } tagNames := make([]string, 0, len(path)) for _, pathElement := range path { if pathElement[0] != '=' { tagName := unescape(pathElement) tagNames = append(tagNames, tagName) } } tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() tagIds, err := vfs.tagNamesToIds(tx, tagNames) if err != nil { log.Fatalf("could not lookup tag IDs: %v.", err) } if tagIds == nil { return nil, fuse.ENOENT } now := time.Now() return &fuse.Attr{Mode: fuse.S_IFDIR | 0755, Nlink: 2, Size: uint64(0), Mtime: uint64(now.Unix()), Mtimensec: uint32(now.Nanosecond())}, fuse.OK }
func (vfs FuseVfs) getDatabaseFileAttr() (*fuse.Attr, fuse.Status) { databasePath := vfs.store.DbPath fileInfo, err := os.Stat(databasePath) if err != nil { log.Fatalf("could not stat database: %v", err) } modTime := fileInfo.ModTime() return &fuse.Attr{Mode: fuse.S_IFLNK | 0755, Size: uint64(fileInfo.Size()), Mtime: uint64(modTime.Unix()), Mtimensec: uint32(modTime.Nanosecond())}, fuse.OK }
func (vfs FuseVfs) openQueryEntryDir(tx *storage.Tx, path []string) ([]fuse.DirEntry, fuse.Status) { log.Infof(2, "BEGIN openQueryEntryDir(%v)", path) defer log.Infof(2, "END openQueryEntryDir(%v)", path) queryText := path[0] expression, err := query.Parse(queryText) if err != nil { log.Fatalf("could not parse query: %v", err) } tagNames, err := query.TagNames(expression) if err != nil { log.Fatalf("could not identify tag names: %v", err) } tags, err := vfs.store.TagsByNames(tx, tagNames) for _, tagName := range tagNames { if !containsTag(tags, tagName) { return nil, fuse.ENOENT } } files, err := vfs.store.FilesForQuery(tx, expression, "", false, false, "name") if err != nil { log.Fatalf("could not query files: %v", err) } entries := make([]fuse.DirEntry, 0, len(files)) for _, file := range files { linkName := vfs.getLinkName(file) entries = append(entries, fuse.DirEntry{Name: linkName, Mode: fuse.S_IFLNK}) } return entries, fuse.OK }
func Run() { helpCommands = commands parser := NewOptionParser(globalOptions, commands) command, options, arguments, err := parser.Parse(os.Args[1:]...) if err != nil { log.Fatal(err) } switch { case options.HasOption("--version"): command = findCommand(commands, "version") case options.HasOption("--help"), command == nil: command = findCommand(commands, "help") } log.Verbosity = options.Count("--verbose") + 1 var databasePath string switch { case options.HasOption("--database"): databasePath = options.Get("--database").Argument case os.Getenv("TMSU_DB") != "": databasePath = os.Getenv("TMSU_DB") default: databasePath, err = findDatabase() if err != nil { log.Fatalf("could not find database: %v", err) } } err, warnings := command.Exec(options, arguments, databasePath) if warnings != nil { for _, warning := range warnings { log.Warn(warning) } } if err != nil { log.Warn(err.Error()) } if err != nil || (warnings != nil && len(warnings) > 0) { os.Exit(1) } }
func (vfs FuseVfs) readTaggedEntryLink(tx *storage.Tx, path []string) (string, fuse.Status) { log.Infof(2, "BEGIN readTaggedEntryLink(%v)", path) defer log.Infof(2, "END readTaggedEntryLink(%v)", path) name := path[len(path)-1] fileId := vfs.parseFileId(name) if fileId == 0 { return "", fuse.ENOENT } file, err := vfs.store.File(tx, fileId) if err != nil { log.Fatalf("could not find file %v in database.", fileId) } return file.Path(), fuse.OK }
func (vfs FuseVfs) tagValueNamesForFiles(tx *storage.Tx, tagName string, files entities.Files) ([]string, error) { tag, err := vfs.store.TagByName(tx, tagName) if err != nil { log.Fatalf("could not look up tag '%v': %v", tagName, err) } if tag == nil { return []string{}, nil } valueIds := make(entities.ValueIds, 0, 10) predicate := func(fileTag entities.FileTag) bool { return fileTag.TagId == tag.Id } for _, file := range files { fileTags, err := vfs.store.FileTagsByFileId(tx, file.Id, false) if err != nil { return nil, fmt.Errorf("could not retrieve file-tags for file '%v': %v", file.Id, err) } for _, valueId := range fileTags.Where(predicate).ValueIds() { valueIds = append(valueIds, valueId) } } values, err := vfs.store.ValuesByIds(tx, valueIds.Uniq()) if err != nil { return nil, fmt.Errorf("could not retrieve values: %v", err) } valueNames := make([]string, 0, len(values)) for _, value := range values { if strings.ContainsAny(value.Name, "/\\") { log.Infof(2, "value '%v' omitted as it contains slashes") continue } valueNames = append(valueNames, value.Name) } return valueNames, nil }
func (vfs FuseVfs) queriesDirectories(tx *storage.Tx) ([]fuse.DirEntry, fuse.Status) { log.Infof(2, "BEGIN queriesDirectories") defer log.Infof(2, "END queriesDirectories") queries, err := vfs.store.Queries(tx) if err != nil { log.Fatalf("could not retrieve queries: %v", err) } entries := make([]fuse.DirEntry, len(queries)) for index, query := range queries { entries[index] = fuse.DirEntry{Name: query.Text, Mode: fuse.S_IFDIR} } if len(queries) < 1 { entries = append(entries, fuse.DirEntry{Name: helpFilename, Mode: fuse.S_IFREG}) } return entries, fuse.OK }
func (vfs FuseVfs) Readlink(name string, context *fuse.Context) (string, fuse.Status) { log.Infof(2, "BEGIN Readlink(%v)", name) defer log.Infof(2, "END Readlink(%v)", name) tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() if name == ".database" { return vfs.readDatabaseFileLink() } path := vfs.splitPath(name) switch path[0] { case tagsDir, queriesDir: return vfs.readTaggedEntryLink(tx, path[1:]) } return "", fuse.ENOENT }
func (vfs FuseVfs) tagDirectories(tx *storage.Tx) ([]fuse.DirEntry, fuse.Status) { log.Infof(2, "BEGIN tagDirectories") defer log.Infof(2, "END tagDirectories") tags, err := vfs.store.Tags(tx) if err != nil { log.Fatalf("Could not retrieve tags: %v", err) } entries := make([]fuse.DirEntry, 0, len(tags)) for _, tag := range tags { tagName := escape(tag.Name) entries = append(entries, fuse.DirEntry{Name: tagName, Mode: fuse.S_IFDIR}) } // show help file until there are three tags if len(tags) < 3 { entries = append(entries, fuse.DirEntry{Name: helpFilename, Mode: fuse.S_IFREG}) } return entries, fuse.OK }
func (vfs FuseVfs) Unlink(name string, context *fuse.Context) fuse.Status { log.Infof(2, "BEGIN Unlink(%v)", name) defer log.Infof(2, "END Unlink(%v)", name) tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() fileId := vfs.parseFileId(name) if fileId == 0 { // can only unlink file symbolic links return fuse.EPERM } file, err := vfs.store.File(tx, fileId) if err != nil { log.Fatal("could not retrieve file '%v': %v", fileId, err) } if file == nil { // reply ok if file doesn't exist otherwise recursive deletes fail return fuse.OK } path := vfs.splitPath(name) switch path[0] { case tagsDir: dirName := path[len(path)-2] var tagName, valueName string if dirName[0] == '=' { tagName = path[len(path)-3] valueName = dirName[1:len(dirName)] } else { tagName = dirName valueName = "" } tag, err := vfs.store.TagByName(tx, tagName) if err != nil { log.Fatal(err) } if tag == nil { log.Fatalf("could not retrieve tag '%v'.", tagName) } value, err := vfs.store.ValueByName(tx, valueName) if err != nil { log.Fatal(err) } if value == nil { log.Fatalf("could not retrieve value '%v'.", valueName) } if err = vfs.store.DeleteFileTag(tx, fileId, tag.Id, value.Id); err != nil { log.Fatal(err) } if err := tx.Commit(); err != nil { log.Fatalf("could not commit transaction: %v", err) } return fuse.OK case queriesDir: return fuse.EPERM } return fuse.ENOSYS }
func (vfs FuseVfs) Rmdir(name string, context *fuse.Context) fuse.Status { log.Infof(2, "BEGIN Rmdir(%v)", name) defer log.Infof(2, "END Rmdir(%v)", name) tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() path := vfs.splitPath(name) switch path[0] { case tagsDir: if len(path) != 2 { // can only remove top-level tag directories return fuse.EPERM } tagName := path[1] tag, err := vfs.store.TagByName(tx, tagName) if err != nil { log.Fatalf("could not retrieve tag '%v': %v", tagName, err) } if tag == nil { return fuse.ENOENT } count, err := vfs.store.FileTagCountByTagId(tx, tag.Id, false) if err != nil { log.Fatalf("could not retrieve file-tag count for tag '%v': %v", tagName, err) } if count > 0 { return fuse.Status(syscall.ENOTEMPTY) } if err := vfs.store.DeleteTag(tx, tag.Id); err != nil { log.Fatalf("could not delete tag '%v': %v", tagName, err) } if err := tx.Commit(); err != nil { log.Fatalf("could not commit transaction: %v", err) } return fuse.OK case queriesDir: if len(path) != 2 { // can only remove top-level queries directories return fuse.EPERM } text := path[1] if err := vfs.store.DeleteQuery(tx, text); err != nil { log.Fatalf("could not remove tag '%v': %v", name, err) } if err := tx.Commit(); err != nil { log.Fatalf("could not commit transaction: %v", err) } return fuse.OK } return fuse.ENOSYS }
func (vfs FuseVfs) getQueryEntryAttr(path []string) (*fuse.Attr, fuse.Status) { log.Infof(2, "BEGIN getQueryEntryAttr(%v)", path) defer log.Infof(2, "END getQueryEntryAttr(%v)", path) if len(path) == 1 && path[0] == helpFilename { now := time.Now() return &fuse.Attr{Mode: fuse.S_IFREG | 0444, Nlink: 1, Size: uint64(len(queryDirHelp)), Mtime: uint64(now.Unix()), Mtimensec: uint32(now.Nanosecond())}, fuse.OK } name := path[len(path)-1] if len(path) > 1 { fileId := vfs.parseFileId(name) if fileId != 0 { return vfs.getFileEntryAttr(fileId) } return nil, fuse.ENOENT } queryText := path[0] if queryText[len(queryText)-1] == ' ' { // prevent multiple entries for same query when typing path in a GUI return nil, fuse.ENOENT } expression, err := query.Parse(queryText) if err != nil { return nil, fuse.ENOENT } tx, err := vfs.store.Begin() if err != nil { log.Fatalf("could not begin transaction: %v", err) } defer tx.Commit() tagNames, err := query.TagNames(expression) if err != nil { log.Fatalf("could not identify tag names: %v", err) } tags, err := vfs.store.TagsByNames(tx, tagNames) for _, tagName := range tagNames { if !containsTag(tags, tagName) { return nil, fuse.ENOENT } } q, err := vfs.store.Query(tx, queryText) if err != nil { log.Fatalf("could not retrieve query '%v': %v", queryText, err) } if q == nil { _, err = vfs.store.AddQuery(tx, queryText) if err != nil { log.Fatalf("could not add query '%v': %v", queryText, err) } } now := time.Now() return &fuse.Attr{Mode: fuse.S_IFDIR | 0755, Nlink: 2, Size: uint64(0), Mtime: uint64(now.Unix()), Mtimensec: uint32(now.Nanosecond())}, fuse.OK }