fileutils

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var CoverImageExtensions = []string{".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"}

CoverImageExtensions contains all supported image extensions for cover files.

View Source
var ShishoSpecialFilePatterns = []string{
	"*.cover.*",
	"*.metadata.json",
}

ShishoSpecialFilePatterns are glob patterns for shisho-generated files (covers, sidecars). These are used to skip special files during scanning and to treat them as ignorable during directory cleanup after book deletion. This slice must not be mutated at runtime.

Functions

func CleanupEmptyDirectory

func CleanupEmptyDirectory(dirPath string, ignoredPatterns ...string) (bool, error)

CleanupEmptyDirectory removes a directory if it's empty or only contains ignored files. ignoredPatterns can include glob patterns like ".*" (dotfiles), ".DS_Store", "Thumbs.db", etc. Returns true if the directory was removed, false if it wasn't empty or didn't exist.

func CleanupEmptyParentDirectories

func CleanupEmptyParentDirectories(startPath, stopAt string, ignoredPatterns ...string) error

CleanupEmptyParentDirectories removes empty parent directories starting from startPath up to (but not including) stopAt. ignoredPatterns are passed to CleanupEmptyDirectory.

func ComputeNewCoverFilename added in v0.0.8

func ComputeNewCoverFilename(oldCoverFilename, newFilePath string) string

ComputeNewCoverFilename computes the new cover filename after a file has been renamed. It preserves the cover's extension while updating the base filename to match the new file path. Returns just the filename (e.g., "book.epub.cover.jpg"), not a full path. Returns empty string if oldCoverFilename is empty.

func CoverExistsWithBaseName

func CoverExistsWithBaseName(dir, baseName string) string

CoverExistsWithBaseName checks if any cover file exists with the given base name, regardless of image extension. This allows users to provide custom covers that won't be overwritten even if the book would extract a different format.

Parameters:

  • dir: the directory to check
  • baseName: the cover base name without extension (e.g., "mybook.epub.cover")

Returns the path to the existing cover file if found, or empty string if no cover exists.

func CreateTemp added in v0.1.1

func CreateTemp(dir, pattern string, perm os.FileMode) (*os.File, error)

CreateTemp is os.CreateTemp with a mode: it creates a new file in dir, named from pattern with the last "*" replaced by a random string (or the string appended when there is no "*"), and opens it for reading and writing.

The file gets perm at creation, filtered by the umask and any ACL the directory passes on, and it is never chmodded afterwards. ACL-managed storage such as a ZFS dataset with aclmode=restricted refuses chmod outright, and a chmod would also override an operator's stricter umask. Create a file with the mode it should end up with instead of fixing the mode afterwards.

The create is exclusive, so a name another writer already holds is skipped rather than opened.

func DirectoryCleanupPatterns added in v0.1.0

func DirectoryCleanupPatterns() []string

DirectoryCleanupPatterns returns the files that cleanup after a scan or a book deletion removes along with an empty directory: JunkFilePatterns plus Shisho's own covers and sidecars (ShishoSpecialFilePatterns).

func ExtractSeriesFromTitle

func ExtractSeriesFromTitle(title string, fileType string) (seriesName string, start, end *float64, unit string, ok bool)

ExtractSeriesFromTitle extracts a series name and atomic number group from a normalized CBZ title. It accepts "v{N}" / "c{N}" suffixes and their ranges.

func GenerateOrganizedFileName

func GenerateOrganizedFileName(opts OrganizedNameOptions, originalFilepath string) string

GenerateOrganizedFileName creates a standardized filename: Title.ext. For M4B files, includes narrator in braces: Title {Narrator}.m4b. Author names are NOT included since files are already inside author-prefixed folders.

func GenerateOrganizedFolderName

func GenerateOrganizedFolderName(opts OrganizedNameOptions) string

GenerateOrganizedFolderName creates a standardized folder name: [Author] Title <number>. For CBZ files, the number is formatted as "v{N}" for volumes or "c{N}" for chapters.

func GenerateUniqueFilepathIfExists added in v0.0.8

func GenerateUniqueFilepathIfExists(path string) string

GenerateUniqueFilepathIfExists returns a unique filepath if the path exists, otherwise returns the original.

func ImageFileResolution added in v0.0.24

func ImageFileResolution(path string) int

ImageFileResolution returns the total pixel count of an image file on disk. Returns 0 if the file cannot be read or decoded.

func ImageResolution added in v0.0.24

func ImageResolution(data []byte) int

ImageResolution returns the total pixel count (width * height) of an image by reading only the image header (no full decode). Returns 0 if the image cannot be decoded.

func IsOrganizedName

func IsOrganizedName(name string) bool

IsOrganizedName checks if a filename/foldername follows the organized naming pattern.

func IsWithin added in v0.6.0

func IsWithin(path, dir string) bool

IsWithin reports whether path is dir or inside it. Unlike a string prefix check, "/books2/a" is not within "/books".

func JunkFilePatterns added in v0.1.0

func JunkFilePatterns() []string

JunkFilePatterns returns the operating system files that directory cleanup deletes so an otherwise empty book directory can be removed: dotfiles (".*" covers .DS_Store), Thumbs.db, and desktop.ini. The list is fixed on purpose. The supplement_exclude_patterns setting only hides files from supplement discovery; if cleanup used it, a user pattern such as "*.txt" would delete the user's files from disk.

func MimeTypeFromExtension

func MimeTypeFromExtension(ext string) string

MimeTypeFromExtension returns the MIME type for a given file extension. Returns empty string if the extension is not recognized.

func MkdirTemp added in v0.1.1

func MkdirTemp(dir, pattern string, perm os.FileMode) (string, error)

MkdirTemp is os.MkdirTemp with a mode. See CreateTemp for why the mode is set at creation.

func MoveFile added in v0.0.8

func MoveFile(src, dst string) error

MoveFile safely moves a file from source to destination. Returns error if move fails.

func MoveFileWithAssociatedFiles added in v0.0.8

func MoveFileWithAssociatedFiles(originalFilePath, newFilePath string) (int, error)

MoveFileWithAssociatedFiles moves a file and its associated files (covers, file sidecar). This does NOT move book sidecars - only file-specific associated files. Returns the number of associated files moved.

func NormalizeImage

func NormalizeImage(data []byte, mimeType string) ([]byte, string, error)

NormalizeImage decodes and re-encodes an image to strip problematic metadata (like gAMA chunks without sRGB in PNG) that cause color rendering issues in browsers. Returns the normalized image data and the new MIME type. If the input is a JPEG, it stays as JPEG to preserve quality. Otherwise, it becomes PNG.

The full pixel decode is the only validation that catches a truncated image: a header-only check (ImageResolution) accepts a body that has an intact signature and header but no complete pixel data. When decoding or re-encoding fails, the original bytes and MIME type are returned together with the error, so a caller that only wants best-effort normalization can keep the bytes and a caller that must not store a broken image can reject them.

func NormalizeSeriesNumberInTitle added in v0.0.40

func NormalizeSeriesNumberInTitle(title string, fileType string) (string, string, bool)

NormalizeSeriesNumberInTitle normalizes volume- or chapter-style number indicators in CBZ titles. For volume indicators (v01, vol.5, volume 12, #001, bare trailing number) the title becomes "Title v{NNN}". For chapter indicators (chapter 5, Ch.5, c042) the title becomes "Title c{NNN}". Returns the normalized title, the parsed unit (models.SeriesNumberUnitVolume or models.SeriesNumberUnitChapter, "" when no match), and whether a number was found. Non-CBZ files are returned unchanged.

func OtherCoverExtensions added in v0.0.51

func OtherCoverExtensions(coverDir, coverBaseName, keepExt string) []string

OtherCoverExtensions returns the existing cover files with the given base name at every CoverImageExtensions entry except keepExt. Cover writers install a replacement at keepExt first and pass this list to the caller, which removes the files only after its database write succeeds.

func RenameOrganizedFile

func RenameOrganizedFile(currentPath string, opts OrganizedNameOptions) (string, error)

RenameOrganizedFile renames an already organized file with new metadata. Also renames associated cover images, file sidecar, AND the book sidecar. Use this for BOOK-level changes (title, author changes) that should update the book sidecar. For FILE-level changes (file name, narrator), use RenameOrganizedFileOnly.

func RenameOrganizedFileOnly

func RenameOrganizedFileOnly(currentPath string, opts OrganizedNameOptions) (string, error)

RenameOrganizedFileOnly renames a file with new metadata, but does NOT rename the book sidecar. Renames the file and its associated cover images and file-specific sidecar. Use this for FILE-level changes (file name, narrator) that should not affect the book sidecar. The book sidecar should only be renamed when book-level metadata (title, author) changes.

func RenameOrganizedFolder

func RenameOrganizedFolder(currentFolderPath string, opts OrganizedNameOptions) (string, error)

RenameOrganizedFolder renames a folder containing organized files.

func ResolveCoverDirForWrite added in v0.0.27

func ResolveCoverDirForWrite(bookFilepath, fileFilepath string) string

ResolveCoverDirForWrite resolves the directory where a cover image should be written. Handles the case where bookFilepath may be a synthetic organized-folder path that does not yet exist on disk — common when scanning root-level files in libraries with OrganizeFileStructure enabled, where the organized directory is created later in the batch. In that case the cover must land alongside the file at filepath.Dir(fileFilepath), which is where extractAndSaveCover wrote the file-embedded cover.

Read-side callers that already have a resolved file.Filepath from the DB should NOT use this helper — prefer the pure-string filepath.Join(filepath.Dir(file.Filepath), coverFilename) which is always correct for both directory-backed and root-level books.

If bookFilepath resolves to a real directory, it's used. Otherwise (stat fails, or bookFilepath is a file), falls back to filepath.Dir(fileFilepath). This is correct for all three scenarios:

  • directory-backed books: bookFilepath is a real dir, use it
  • root-level books (rescan): bookFilepath == fileFilepath, fall back gives filepath.Dir(fileFilepath) = library dir, same as before
  • root-level books (new file, synthetic path): stat fails, fall back gives filepath.Dir(fileFilepath) = library dir where the file lives

func SplitNames

func SplitNames(s string) []string

SplitNames splits a string of names by common delimiters (comma and semicolon), trims whitespace from each name, and returns non-empty names. This is used for parsing author and narrator lists from metadata.

func UndoOrganizedMove added in v0.1.1

func UndoOrganizedMove(originalPath, newPath string, includeBookSidecar bool) error

UndoOrganizedMove moves a file that organizing moved or renamed from newPath back to originalPath, with its covers and file sidecar, and its book sidecar when includeBookSidecar is set (pass the same value the move used: true for OrganizeRootLevelFile, MoveFileIntoOrganizedFolder, and RenameOrganizedFile, false for RenameOrganizedFileOnly). Callers use it when recording the new path in the database fails, so disk and database agree again. The original directory is recreated if organizing removed it, and newPath's directory is removed if the undo leaves it empty.

func WalkDirFollowingRoot added in v0.6.0

func WalkDirFollowingRoot(root string, fn fs.WalkDirFunc) error

WalkDirFollowingRoot is filepath.WalkDir, except that a root that is a symlink to a directory is walked into. filepath.WalkDir reports such a root as a single entry, so a library path pointing through a symlink would look empty and its Books would be treated as deleted. Every path passed to fn is clean, the root's included.

func WriteFileAtomic added in v0.0.51

func WriteFileAtomic(path string, data []byte, perm os.FileMode) error

WriteFileAtomic writes data to path so that a reader never observes a partial file: the bytes go to a temporary file in the same directory, which then replaces path with a rename. On failure the temporary file is removed and whatever was at path before is left untouched. Every cover write (scanner, Identify, page selection, upload) goes through it so a failed replacement never destroys a working cover. Because the replacement is a rename, a symlink or hard link at path is replaced by a new regular file rather than written through. The file is created with perm, never chmodded (see CreateTemp).

Types

type OrganizeFileResult

type OrganizeFileResult struct {
	OriginalPath  string
	NewPath       string
	FolderCreated bool
	Moved         bool
	CoversMoved   int
	CoversError   error
}

OrganizeFileResult contains the results of organizing a file.

func MoveFileIntoOrganizedFolder added in v0.0.41

func MoveFileIntoOrganizedFolder(originalPath, targetFolder string, opts OrganizedNameOptions) (*OrganizeFileResult, error)

MoveFileIntoOrganizedFolder moves a file (and its associated covers and file sidecar) into the given target folder, computing the destination filename from opts. The target folder is created if missing. After a successful move the source directory is cleaned up if empty. Use this when the destination folder is already known (e.g. promoting a root-level file into an existing book folder), so the caller doesn't have to re-derive the folder from opts and risk landing in a different folder than intended.

func OrganizeRootLevelFile

func OrganizeRootLevelFile(originalPath string, opts OrganizedNameOptions) (*OrganizeFileResult, error)

OrganizeRootLevelFile creates a folder (derived from opts) and moves a root-level file into it. Use this when the destination folder should be computed from the file's metadata. To move a file into an already-known folder (e.g. joining an existing directory-backed book), use MoveFileIntoOrganizedFolder.

type OrganizedNameOptions

type OrganizedNameOptions struct {
	AuthorNames      []string // Author names as strings for file naming
	NarratorNames    []string // Narrator names for M4B file naming
	Title            string
	SeriesNumber     *float64
	SeriesNumberEnd  *float64
	SeriesNumberUnit *string // for CBZ: models.SeriesNumberUnitVolume or models.SeriesNumberUnitChapter; nil treated as volume
	FileType         string  // for determining number formatting

	// Claimed reports whether another record already holds a path, such as
	// a files row for a different file. The organize helpers skip a claimed
	// destination with a numbered suffix, the same as one that exists on
	// disk, so a move never lands on a path the database gives to another
	// file. Nil means only the disk is checked.
	Claimed func(path string) bool
}

OrganizedNameOptions contains the data needed to generate organized file/folder names.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL