func showUsage(store *storage.Storage, tx *storage.Tx, colour bool) error { tagUsages, err := store.TagUsage(tx) if err != nil { return fmt.Errorf("could not retrieve tag usage: %v", err) } maxLength := 0 maxCountWidth := 0 for _, tagUsage := range tagUsages { countWidth := int(math.Log(float64(tagUsage.FileCount))) if countWidth > maxCountWidth { maxCountWidth = countWidth } if len(tagUsage.Name) > maxLength { maxLength = len(tagUsage.Name) } } fmt.Println() for _, tagUsage := range tagUsages { fileCount := strconv.FormatUint(uint64(tagUsage.FileCount), 10) if colour { fileCount = ansi.Yellow(fileCount) } fmt.Printf(" %*s %*v\n", -maxLength, tagUsage.Name, maxCountWidth, fileCount) } return nil }
func listValuesForTag(store *storage.Storage, tx *storage.Tx, tagName string, showCount, onePerLine bool) error { tag, err := store.TagByName(tx, 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) } log.Infof(2, "retrieving values for tag '%v'.", tagName) values, err := store.ValuesByTag(tx, tag.Id) if err != nil { return fmt.Errorf("could not retrieve values for tag '%v': %v", tagName, err) } if showCount { fmt.Println(len(values)) } else { if onePerLine { for _, value := range values { fmt.Println(escape(value.Name, '=', ' ')) } } else { valueNames := make([]string, len(values)) for index, value := range values { valueNames[index] = escape(value.Name, '=', ' ') } terminal.PrintColumns(valueNames) } } return nil }
func repairModified(store *storage.Storage, tx *storage.Tx, modified entities.Files, pretend bool, settings entities.Settings) error { log.Infof(2, "repairing modified files") for _, dbFile := range modified { stat, err := os.Stat(dbFile.Path()) if err != nil { return err } fingerprint, err := fingerprint.Create(dbFile.Path(), settings.FileFingerprintAlgorithm(), settings.DirectoryFingerprintAlgorithm(), settings.SymlinkFingerprintAlgorithm()) if err != nil { log.Warnf("%v: could not create fingerprint: %v", dbFile.Path(), err) continue } if !pretend { _, err := store.UpdateFile(tx, dbFile.Id, dbFile.Path(), fingerprint, stat.ModTime(), stat.Size(), stat.IsDir()) if err != nil { return fmt.Errorf("%v: could not update file in database: %v", dbFile.Path(), err) } } fmt.Printf("%v: updated fingerprint\n", dbFile.Path()) } return nil }
func statusDatabase(store *storage.Storage, tx *storage.Tx, dirOnly, followSymlinks bool) (*StatusReport, error) { report := NewReport() log.Info(2, "retrieving all files from database.") files, err := store.Files(tx, "name") if err != nil { return nil, fmt.Errorf("could not retrieve files: %v", err) } err = statusCheckFiles(files, report) if err != nil { return nil, err } tree := _path.NewTree() for _, file := range files { tree.Add(file.Path(), file.IsDir) } topLevelPaths := tree.TopLevel().Paths() if err != nil { return nil, err } for _, path := range topLevelPaths { if err = findNewFiles(path, report, dirOnly, followSymlinks); err != nil { return nil, err } } return report, nil }
func listAllTags(store *storage.Storage, tx *storage.Tx, showCount, onePerLine bool) error { log.Info(2, "retrieving all tags.") if showCount { count, err := store.TagCount(tx) if err != nil { return fmt.Errorf("could not retrieve tag count: %v", err) } fmt.Println(count) } else { tags, err := store.Tags(tx) if err != nil { return fmt.Errorf("could not retrieve tags: %v", err) } if onePerLine { for _, tag := range tags { fmt.Println(escape(tag.Name, '=', ' ')) } } else { tagNames := make([]string, len(tags)) for index, tag := range tags { tagNames[index] = escape(tag.Name, '=', ' ') } terminal.PrintColumns(tagNames) } } return nil }
func manualRepairFile(store *storage.Storage, tx *storage.Tx, file *entities.File, toPath string) error { var fingerprint fingerprint.Fingerprint var modTime time.Time var size int64 var isDir bool stat, err := os.Stat(toPath) if err != nil { switch { case os.IsPermission(err): return fmt.Errorf("%v: permission denied", toPath) case os.IsNotExist(err): return fmt.Errorf("%v: file not found", toPath) default: return err } modTime = file.ModTime size = file.Size isDir = file.IsDir } else { modTime = stat.ModTime() size = stat.Size() isDir = stat.IsDir() } _, err = store.UpdateFile(tx, file.Id, toPath, fingerprint, modTime, size, isDir) return err }
func findUntagged(store *storage.Storage, tx *storage.Tx, paths []string, recursive bool) error { for _, path := range paths { absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("%v: could not get absolute path: %v", path, err) } //TODO PERF no need to retrieve file: we merely need to know it exists file, err := store.FileByPath(tx, absPath) if err != nil { return fmt.Errorf("%v: could not retrieve file: %v", path, err) } if file == nil { relPath := _path.Rel(absPath) fmt.Println(relPath) } if recursive { entries, err := directoryEntries(path) if err != nil { return err } findUntagged(store, tx, entries, true) } } return nil }
func listAllValues(store *storage.Storage, tx *storage.Tx, showCount, onePerLine bool) error { log.Info(2, "retrieving all values.") if showCount { count, err := store.ValueCount(tx) if err != nil { return fmt.Errorf("could not retrieve value count: %v", err) } fmt.Println(count) } else { values, err := store.Values(tx) if err != nil { return fmt.Errorf("could not retrieve values: %v", err) } if onePerLine { for _, value := range values { fmt.Println(escape(value.Name)) } } else { valueNames := make([]string, len(values)) for index, value := range values { valueNames[index] = escape(value.Name) } terminal.PrintColumns(valueNames) } } return nil }
func listImplications(store *storage.Storage, tx *storage.Tx, colour bool) error { log.Infof(2, "retrieving tag implications.") implications, err := store.Implications(tx) if err != nil { return fmt.Errorf("could not retrieve implications: %v", err) } width := 0 for _, implication := range implications { length := len(implication.ImplyingTag.Name) if implication.ImplyingValue.Id != 0 { length += 1 + len(implication.ImplyingValue.Name) } if length > width { width = length } } if len(implications) > 0 { for _, implication := range implications { implying := formatTagValueName(implication.ImplyingTag.Name, implication.ImplyingValue.Name, colour, false, true) implied := formatTagValueName(implication.ImpliedTag.Name, implication.ImpliedValue.Name, colour, true, false) fmt.Printf("%*v -> %v\n", width, implying, implied) } } return nil }
func findDuplicatesInDb(store *storage.Storage, tx *storage.Tx) error { log.Info(2, "identifying duplicate files.") fileSets, err := store.DuplicateFiles(tx) if err != nil { return fmt.Errorf("could not identify duplicate files: %v", err) } log.Infof(2, "found %v sets of duplicate files.", len(fileSets)) for index, fileSet := range fileSets { if index > 0 { fmt.Println() } fmt.Printf("Set of %v duplicates:\n", len(fileSet)) for _, file := range fileSet { relPath := _path.Rel(file.Path()) fmt.Printf(" %v\n", relPath) } } return nil }
func repairMoved(store *storage.Storage, tx *storage.Tx, missing entities.Files, searchPaths []string, pretend bool, settings entities.Settings) error { log.Infof(2, "repairing moved files") if len(missing) == 0 || len(searchPaths) == 0 { // don't bother enumerating filesystem if nothing to do return nil } pathsBySize, err := buildPathBySizeMap(searchPaths) if err != nil { return err } for index, dbFile := range missing { log.Infof(2, "%v: searching for new location", dbFile.Path()) pathsOfSize := pathsBySize[dbFile.Size] log.Infof(2, "%v: file is of size %v, identified %v files of this size", dbFile.Path(), dbFile.Size, len(pathsOfSize)) for _, candidatePath := range pathsOfSize { candidateFile, err := store.FileByPath(tx, candidatePath) if err != nil { return err } if candidateFile != nil { // file is already tagged continue } stat, err := os.Stat(candidatePath) if err != nil { return fmt.Errorf("%v: could not stat file: %v", candidatePath, err) } fingerprint, err := fingerprint.Create(candidatePath, settings.FileFingerprintAlgorithm(), settings.DirectoryFingerprintAlgorithm(), settings.SymlinkFingerprintAlgorithm()) if err != nil { return fmt.Errorf("%v: could not create fingerprint: %v", candidatePath, err) } if fingerprint == dbFile.Fingerprint { if !pretend { _, err := store.UpdateFile(tx, 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", dbFile.Path(), err) } } fmt.Printf("%v: updated path to %v\n", dbFile.Path(), candidatePath) missing[index] = nil break } } } return nil }
func deleteUntaggedFiles(store *storage.Storage, tx *storage.Tx, files entities.Files) error { log.Infof(2, "purging untagged files") fileIds := make([]entities.FileId, len(files)) for index, file := range files { fileIds[index] = file.Id } return store.DeleteUntaggedFiles(tx, fileIds) }
func createTag(store *storage.Storage, tx *storage.Tx, tagName string) (*entities.Tag, error) { tag, err := store.AddTag(tx, tagName) if err != nil { return nil, err } log.Warnf("new tag '%v'", tagName) return tag, nil }
func createValue(store *storage.Storage, tx *storage.Tx, valueName string) (*entities.Value, error) { value, err := store.AddValue(tx, valueName) if err != nil { return nil, err } log.Warnf("new value '%v'", valueName) return value, nil }
func deleteImplications(store *storage.Storage, tx *storage.Tx, tagArgs []string) (error, warnings) { log.Infof(2, "loading settings") implyingTagArg := tagArgs[0] impliedTagArgs := tagArgs[1:] implyingTagName, implyingValueName := parseTagEqValueName(implyingTagArg) implyingTag, err := store.TagByName(tx, implyingTagName) if err != nil { return err, nil } if implyingTag == nil { return NoSuchTagError{implyingTagName}, nil } implyingValue, err := store.ValueByName(tx, implyingValueName) if err != nil { return err, nil } if implyingValue == nil { return NoSuchValueError{implyingValueName}, nil } warnings := make(warnings, 0, 10) for _, impliedTagArg := range impliedTagArgs { log.Infof(2, "removing tag implication %v -> %v.", implyingTagArg, impliedTagArg) impliedTagName, impliedValueName := parseTagEqValueName(impliedTagArg) impliedTag, err := store.TagByName(tx, impliedTagName) if err != nil { return err, warnings } if impliedTag == nil { warnings = append(warnings, fmt.Sprintf("no such tag '%v'", impliedTagName)) } impliedValue, err := store.ValueByName(tx, impliedValueName) if err != nil { return err, warnings } if impliedValue == nil { warnings = append(warnings, fmt.Sprintf("no such value '%v'", impliedValueName)) } if err := store.DeleteImplication(tx, entities.TagIdValueIdPair{implyingTag.Id, implyingValue.Id}, entities.TagIdValueIdPair{impliedTag.Id, impliedValue.Id}); err != nil { return fmt.Errorf("could not delete tag implication of %v to %v: %v", implyingTagArg, impliedTagArg, err), warnings } } return nil, warnings }
func listAllSettings(store *storage.Storage, tx *storage.Tx) error { settings, err := store.Settings(tx) if err != nil { return fmt.Errorf("could not retrieve settings: %v", err) } for _, setting := range settings { printSettingAndValue(setting.Name, setting.Value) } return nil }
func mergeValues(store *storage.Storage, tx *storage.Tx, sourceValueNames []string, destValueName string) (error, warnings) { destValue, err := store.ValueByName(tx, destValueName) if err != nil { return fmt.Errorf("could not retrieve value '%v': %v", destValueName, err), nil } if destValue == nil { return fmt.Errorf("no such value '%v'", destValueName), nil } warnings := make(warnings, 0, 10) for _, sourceValueName := range sourceValueNames { if sourceValueName == destValueName { warnings = append(warnings, fmt.Sprintf("cannot merge value '%v' into itself", sourceValueName)) continue } sourceValue, err := store.ValueByName(tx, sourceValueName) if err != nil { return fmt.Errorf("could not retrieve value '%v': %v", sourceValueName, err), warnings } if sourceValue == nil { warnings = append(warnings, fmt.Sprintf("no such value '%v'", sourceValueName)) continue } log.Infof(2, "finding files tagged with value '%v'.", sourceValueName) fileTags, err := store.FileTagsByValueId(tx, sourceValue.Id) if err != nil { return fmt.Errorf("could not retrieve files for value '%v': %v", sourceValueName, err), warnings } log.Infof(2, "applying value '%v' to these files.", destValueName) for _, fileTag := range fileTags { if _, err = store.AddFileTag(tx, fileTag.FileId, fileTag.TagId, destValue.Id); err != nil { return fmt.Errorf("could not apply value '%v' to file #%v: %v", destValueName, fileTag.FileId, err), warnings } } log.Infof(2, "deleting value '%v'.", sourceValueName) if err = store.DeleteValue(tx, sourceValue.Id); err != nil { return fmt.Errorf("could not delete value '%v': %v", sourceValueName, err), warnings } } return nil, warnings }
func manualRepair(store *storage.Storage, tx *storage.Tx, fromPath, toPath string, pretend bool) error { absFromPath, err := filepath.Abs(fromPath) if err != nil { return fmt.Errorf("%v: could not determine absolute path", err) } absToPath, err := filepath.Abs(toPath) if err != nil { return fmt.Errorf("%v: could not determine absolute path", err) } log.Infof(2, "retrieving files under '%v' from the database", fromPath) dbFile, err := store.FileByPath(tx, absFromPath) if err != nil { return fmt.Errorf("%v: could not retrieve file: %v", fromPath, err) } if dbFile != nil { log.Infof(2, "%v: updating to %v", fromPath, toPath) if !pretend { if err := manualRepairFile(store, tx, dbFile, absToPath); err != nil { return err } } } dbFiles, err := store.FilesByDirectory(tx, absFromPath) if err != nil { return fmt.Errorf("could not retrieve files from storage: %v", err) } for _, dbFile = range dbFiles { relFileFromPath := _path.Rel(dbFile.Path()) absFileToPath := strings.Replace(dbFile.Path(), absFromPath, absToPath, 1) relFileToPath := _path.Rel(absFileToPath) log.Infof(2, "%v: updating to %v", relFileFromPath, relFileToPath) if !pretend { if err := manualRepairFile(store, tx, dbFile, absFileToPath); err != nil { return err } } } return nil }
func untagPathsAll(store *storage.Storage, tx *storage.Tx, paths []string, recursive, followSymlinks bool) (error, warnings) { warnings := make(warnings, 0, 10) for _, path := range paths { absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("%v: could not get absolute path: %v", path, err), warnings } log.Infof(2, "%v: resolving path", path) stat, err := os.Lstat(absPath) if err != nil { switch { case os.IsNotExist(err), os.IsPermission(err): // ignore default: return err, nil } } else if stat.Mode()&os.ModeSymlink != 0 && followSymlinks { absPath, err = _path.Dereference(absPath) if err != nil { return err, nil } } file, err := store.FileByPath(tx, absPath) if err != nil { return fmt.Errorf("%v: could not retrieve file: %v", path, err), warnings } if file == nil { warnings = append(warnings, fmt.Sprintf("%v: file is not tagged.", path)) continue } log.Infof(2, "%v: removing all tags.", path) if err := store.DeleteFileTagsByFileId(tx, file.Id); err != nil { return fmt.Errorf("%v: could not remove file's tags: %v", path, err), warnings } if recursive { childFiles, err := store.FilesByDirectory(tx, file.Path()) if err != nil { return fmt.Errorf("%v: could not retrieve files for directory: %v", path, err), warnings } for _, childFile := range childFiles { if err := store.DeleteFileTagsByFileId(tx, childFile.Id); err != nil { return fmt.Errorf("%v: could not remove file's tags: %v", childFile.Path(), err), warnings } } } } return nil, warnings }
func printSetting(store *storage.Storage, tx *storage.Tx, name string) error { if name == "" { return fmt.Errorf("setting name must be specified") } setting, err := store.Setting(tx, name) if err != nil { return fmt.Errorf("could not retrieve setting '%v'", err) } if setting == nil { return fmt.Errorf("no such setting '%v'", name) } printSettingAndValue(setting.Name, setting.Value) return nil }
func listValuesForTags(store *storage.Storage, tx *storage.Tx, tagNames []string, showCount, onePerLine bool) (error, warnings) { warnings := make(warnings, 0, 10) for _, tagName := range tagNames { tag, err := store.TagByName(tx, tagName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", tagName, err), warnings } if tag == nil { warnings = append(warnings, fmt.Sprintf("no such tag, '%v'.", tagName)) continue } log.Infof(2, "retrieving values for tag '%v'.", tagName) values, err := store.ValuesByTag(tx, tag.Id) if err != nil { return fmt.Errorf("could not retrieve values for tag '%v': %v", tagName, err), warnings } if showCount { fmt.Printf("%v: %v\n", tagName, len(values)) } else { if onePerLine { fmt.Println(tagName) for _, value := range values { fmt.Println(escape(value.Name, '=', ' ')) } fmt.Println() } else { valueNames := make([]string, len(values)) for index, value := range values { valueNames[index] = escape(value.Name, '=', ' ') } fmt.Printf("%v: %v\n", tagName, strings.Join(valueNames, " ")) } } } return nil, warnings }
func statusPaths(store *storage.Storage, tx *storage.Tx, paths []string, dirOnly bool) (*StatusReport, error) { report := NewReport() 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(tx, absPath) if err != nil { return nil, fmt.Errorf("%v: could not retrieve file: %v", path, err) } if file != nil { err = statusCheckFile(file, report) if err != nil { return nil, err } } if !dirOnly { log.Infof(2, "%v: retrieving files from database.", path) files, err := store.FilesByDirectory(tx, absPath) if err != nil { return nil, fmt.Errorf("%v: could not retrieve files for directory: %v", path, err) } err = statusCheckFiles(files, report) if err != nil { return nil, err } } err = findNewFiles(absPath, report, dirOnly) if err != nil { return nil, err } } return report, nil }
func repairMissing(store *storage.Storage, tx *storage.Tx, missing entities.Files, pretend, force bool) error { for _, dbFile := range missing { if dbFile == nil { continue } if force { if !pretend { if err := store.DeleteFileTagsByFileId(tx, dbFile.Id); err != nil { return fmt.Errorf("%v: could not delete file-tags: %v", dbFile.Path(), err) } } fmt.Printf("%v: removed\n", dbFile.Path()) } else { fmt.Printf("%v: missing\n", dbFile.Path()) } } return nil }
func renameTag(store *storage.Storage, tx *storage.Tx, currentName, newName string) error { sourceTag, err := store.TagByName(tx, currentName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", currentName, err) } if sourceTag == nil { return fmt.Errorf("no such tag '%v'", currentName) } destTag, err := store.TagByName(tx, newName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", newName, err) } if destTag != nil { return fmt.Errorf("tag '%v' already exists", newName) } log.Infof(2, "renaming tag '%v' to '%v'.", currentName, newName) _, err = store.RenameTag(tx, sourceTag.Id, newName) if err != nil { return fmt.Errorf("could not rename tag '%v' to '%v': %v", currentName, newName, err) } return nil }
func renameValue(store *storage.Storage, tx *storage.Tx, currentName, newName string) error { sourceValue, err := store.ValueByName(tx, currentName) if err != nil { return fmt.Errorf("could not retrieve value '%v': %v", currentName, err) } if sourceValue == nil { return fmt.Errorf("no such value '%v'", currentName) } destValue, err := store.ValueByName(tx, newName) if err != nil { return fmt.Errorf("could not retrieve value '%v': %v", newName, err) } if destValue != nil { return fmt.Errorf("value '%v' already exists", newName) } log.Infof(2, "renaming value '%v' to '%v'.", currentName, newName) _, err = store.RenameValue(tx, sourceValue.Id, newName) if err != nil { return fmt.Errorf("could not rename value '%v' to '%v': %v", currentName, newName, err) } return nil }
func amendSetting(store *storage.Storage, tx *storage.Tx, name, value string) error { if name == "" { return fmt.Errorf("setting name must be specified") } if value == "" { return fmt.Errorf("setting '%v' value must be specified", name) } setting, err := store.Setting(tx, name) if err != nil { return fmt.Errorf("could not retrieve setting '%v'", err) } if setting == nil { return fmt.Errorf("no such setting '%v'", name) } if _, err = store.UpdateSetting(tx, name, value); err != nil { return fmt.Errorf("could not update setting '%v': %v", name, err) } return nil }
func rationalizeFileTags(store *storage.Storage, tx *storage.Tx, files entities.Files) error { log.Infof(2, "rationalizing file tags") for _, file := range files { fileTags, err := store.FileTagsByFileId(tx, file.Id, false) if err != nil { return fmt.Errorf("could not determine tags for file '%v': %v", file.Path(), err) } for _, fileTag := range fileTags { if fileTag.Implicit && fileTag.Explicit { log.Infof(2, "%v: removing explicit tagging %v as implicit tagging exists", file.Path(), fileTag.TagId) if err := store.DeleteFileTag(tx, fileTag.FileId, fileTag.TagId, fileTag.ValueId); err != nil { return fmt.Errorf("could not delete file tag for file %v, tag %v and value %v", fileTag.FileId, fileTag.TagId, fileTag.ValueId) } } } } return nil }
func deleteValue(store *storage.Storage, tx *storage.Tx, valueArgs []string) (error, warnings) { warnings := make(warnings, 0, 10) for _, valueArg := range valueArgs { valueName := parseTagOrValueName(valueArg) value, err := store.ValueByName(tx, valueName) if err != nil { return fmt.Errorf("could not retrieve value '%v': %v", valueName, err), warnings } if value == nil { warnings = append(warnings, fmt.Sprintf("no such value '%v'", valueName)) continue } if err = store.DeleteValue(tx, value.Id); err != nil { return fmt.Errorf("could not delete value '%v': %v", valueName, err), warnings } } return nil, warnings }
func deleteTag(store *storage.Storage, tx *storage.Tx, tagArgs []string) (error, warnings) { warnings := make(warnings, 0, 10) for _, tagArg := range tagArgs { tagName := parseTagOrValueName(tagArg) tag, err := store.TagByName(tx, tagName) if err != nil { return fmt.Errorf("could not retrieve tag '%v': %v", tagName, err), warnings } if tag == nil { warnings = append(warnings, fmt.Sprintf("no such tag '%v'", tagName)) continue } err = store.DeleteTag(tx, tag.Id) if err != nil { return fmt.Errorf("could not delete tag '%v': %v", tagName, err), warnings } } return nil, warnings }
func findUntaggedFunc(store *storage.Storage, tx *storage.Tx, paths []string, recursive, followSymlinks bool, action func(absPath string)) error { for _, path := range paths { absPath, err := filepath.Abs(path) if err != nil { return fmt.Errorf("%v: could not get absolute path: %v", path, err) } if followSymlinks { log.Infof(2, "%v: resolving path", path) absPath, err = _path.Dereference(absPath) if err != nil { return fmt.Errorf("%v: could not dereference path: %v", path, err) } } //TODO PERF no need to retrieve file: we merely need to know it exists file, err := store.FileByPath(tx, absPath) if err != nil { return fmt.Errorf("%v: could not retrieve file: %v", path, err) } if file == nil { action(absPath) } if recursive { entries, err := directoryEntries(path) if err != nil { return err } findUntaggedFunc(store, tx, entries, true, followSymlinks, action) } } return nil }