diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e87e08d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,44 @@ +version: "2" +run: + tests: false +linters: + default: all + disable: + - revive + - noinlineerr + - mnd + - tagliatelle + - ireturn + - godox + - exhaustruct + - depguard + settings: + wsl_v5: + allow-first-in-block: true + allow-whole-block: false + branch-max-lines: 2 + lll: + line-length: 120 + staticcheck: + checks: + - -SA1029 + varnamelen: + ignore-decls: + - tx *sqlx.Tx + wrapcheck: + ignore-sig-regexps: + - \.Write\( + - \.WriteString\( + - \.WriteJSON\( + - \.WriteHTML\( + - \.Redirect\( + - \.Error\( + - \.ErrorV0\( +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofmt + - gofumpt + - goimports diff --git a/cmd/faketunes/faketunes.go b/cmd/faketunes/faketunes.go index 40bcc9a..e9f6de6 100644 --- a/cmd/faketunes/faketunes.go +++ b/cmd/faketunes/faketunes.go @@ -46,7 +46,9 @@ func main() { // CTRL+C handler. interrupt := make(chan os.Signal, 1) signal.Notify(interrupt) + shutdownDone := make(chan bool, 1) + go func() { signalThing := <-interrupt if signalThing == syscall.SIGTERM || signalThing == syscall.SIGINT { diff --git a/internal/application/application.go b/internal/application/application.go index 2a950d6..4a4b9cc 100644 --- a/internal/application/application.go +++ b/internal/application/application.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "runtime" + "strconv" "sync" "github.com/sirupsen/logrus" @@ -34,6 +35,7 @@ func (a *App) Logger() *logrus.Entry { func New(ctx context.Context) *App { var m runtime.MemStats + runtime.ReadMemStats(&m) app := new(App) @@ -48,7 +50,7 @@ func New(ctx context.Context) *App { app.logger = logger.WithContext(ctx).WithFields(logrus.Fields{ "memalloc": fmt.Sprintf("%dMB", m.Alloc/1024/1024), "memsys": fmt.Sprintf("%dMB", m.Sys/1024/1024), - "numgc": fmt.Sprintf("%d", m.NumGC), + "numgc": strconv.FormatUint(uint64(m.NumGC), 10), }) app.ctx = ctx diff --git a/internal/configuration/config.go b/internal/configuration/config.go index 027a3dc..0d18b9f 100644 --- a/internal/configuration/config.go +++ b/internal/configuration/config.go @@ -40,6 +40,7 @@ func New() (*Config, error) { } config := new(Config) + err = yaml.Unmarshal(rawConfig, config) if err != nil { return nil, fmt.Errorf("%w: %w (%w)", ErrConfiguration, ErrCantParseConfigFile, err) diff --git a/internal/domains/cacher/files.go b/internal/domains/cacher/files.go index 1390b59..6c27245 100644 --- a/internal/domains/cacher/files.go +++ b/internal/domains/cacher/files.go @@ -2,6 +2,7 @@ package cacher import ( "crypto/md5" + "encoding/hex" "fmt" "os" "path/filepath" @@ -31,7 +32,7 @@ func (c *Cacher) getFile(sourcePath string) (*models.CacheItem, error) { keyData := fmt.Sprintf("%s:%d", sourcePath, sourceFileInfo.ModTime().UnixNano()) hash := md5.Sum([]byte(keyData)) - cacheKey := fmt.Sprintf("%x", hash) + cacheKey := hex.EncodeToString(hash[:]) cacheFilePath := filepath.Join(c.cacheDir, cacheKey+".m4a") c.itemsMutex.Lock() @@ -73,6 +74,7 @@ func (c *Cacher) getFile(sourcePath string) (*models.CacheItem, error) { // File does not exist on disk, need to transcode. // Register in the queue c.transcoder.QueueChannel() <- struct{}{} + defer func() { <-c.transcoder.QueueChannel() }() @@ -94,7 +96,7 @@ func (c *Cacher) getFile(sourcePath string) (*models.CacheItem, error) { c.updateCachedStat(sourcePath, size) // TODO: run cleanup on inotify events. - // c.cleanup() + c.cleanup() return item, nil } diff --git a/internal/domains/cacher/stats.go b/internal/domains/cacher/stats.go index 285f2b9..870a5ba 100644 --- a/internal/domains/cacher/stats.go +++ b/internal/domains/cacher/stats.go @@ -2,6 +2,7 @@ package cacher import ( "crypto/md5" + "encoding/hex" "fmt" "os" "path/filepath" @@ -10,7 +11,7 @@ import ( "source.hodakov.me/hdkv/faketunes/internal/domains/cacher/models" ) -// getStat returns file size without triggering conversion (for ls/stat) +// getStat returns file size without triggering conversion (for ls/stat). func (c *Cacher) GetStat(sourcePath string) (int64, error) { c.statMutex.RLock() defer c.statMutex.RUnlock() @@ -28,7 +29,7 @@ func (c *Cacher) GetStat(sourcePath string) (int64, error) { keyData := fmt.Sprintf("%s:%d", sourcePath, info.ModTime().UnixNano()) hash := md5.Sum([]byte(keyData)) - key := fmt.Sprintf("%x", hash) + key := hex.EncodeToString(hash[:]) cachePath := filepath.Join(c.cacheDir, key+".m4a") // Check if converted file exists and is valid @@ -44,7 +45,7 @@ func (c *Cacher) GetStat(sourcePath string) (int64, error) { return info.Size(), nil } -// updateCachedStat updates the stat cache +// updateCachedStat updates the stat cache. func (c *Cacher) updateCachedStat(sourcePath string, size int64) { c.statMutex.Lock() defer c.statMutex.Unlock() @@ -55,7 +56,7 @@ func (c *Cacher) updateCachedStat(sourcePath string, size int64) { } } -// getCachedStat returns cached file stats +// getCachedStat returns cached file stats. func (c *Cacher) getCachedStat(sourcePath string) (int64, bool) { c.statMutex.RLock() defer c.statMutex.RUnlock() @@ -63,5 +64,6 @@ func (c *Cacher) getCachedStat(sourcePath string) (int64, bool) { if stat, ok := c.stat[sourcePath]; ok { return stat.Size, true } + return 0, false } diff --git a/internal/domains/filesystem.go b/internal/domains/filesystem.go index 66bdd7a..7f19e1c 100644 --- a/internal/domains/filesystem.go +++ b/internal/domains/filesystem.go @@ -2,4 +2,4 @@ package domains const FilesystemName = "filesystem" -type Filesystem interface{} +type Filesystem any diff --git a/internal/domains/filesystem/directories.go b/internal/domains/filesystem/directories.go index 2e3196a..138f85f 100644 --- a/internal/domains/filesystem/directories.go +++ b/internal/domains/filesystem/directories.go @@ -35,7 +35,7 @@ func (f *FS) prepareDirectories() error { // Create the structure for the virtual filesystem. for _, dir := range []string{f.destinationDir, f.cacheDir, f.metadataDir} { - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, 0o755); err != nil { f.app.Logger().WithField("path", dir).Error("Operation on directory was unsuccessful") return fmt.Errorf("%w: %w (%w)", ErrFilesystem, ErrFailedToCreateDestinationDirectory, err) diff --git a/internal/domains/filesystem/file.go b/internal/domains/filesystem/file.go index b8aa43b..7888d97 100644 --- a/internal/domains/filesystem/file.go +++ b/internal/domains/filesystem/file.go @@ -2,6 +2,7 @@ package filesystem import ( "context" + "errors" "io" "os" "sync" @@ -33,7 +34,7 @@ func (fi *File) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResu } n, err := fi.file.Read(dest) - if err != nil && err != io.EOF { + if err != nil && !errors.Is(err, io.EOF) { return nil, syscall.EIO } diff --git a/internal/domains/filesystem/music_app_metadata.go b/internal/domains/filesystem/music_app_metadata.go index 235c728..008945c 100644 --- a/internal/domains/filesystem/music_app_metadata.go +++ b/internal/domains/filesystem/music_app_metadata.go @@ -30,7 +30,7 @@ var ( func (m *MusicAppMetadataFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { info, err := os.Stat(m.path) if err != nil { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = m.StableAttr().Ino out.Size = 0 @@ -57,7 +57,7 @@ func (m *MusicAppMetadataFile) Getattr(ctx context.Context, fh fs.FileHandle, ou func (m *MusicAppMetadataFile) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { info, err := os.Stat(m.path) if err != nil { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = m.StableAttr().Ino out.Size = 0 @@ -90,7 +90,7 @@ func (m *MusicAppMetadataFile) Create(ctx context.Context, name string, flags ui Ino: m.f.nextInode(), }) - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = ch.StableAttr().Ino out.Size = 0 @@ -104,12 +104,12 @@ func (m *MusicAppMetadataFile) Create(ctx context.Context, name string, flags ui func (m *MusicAppMetadataFile) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { if _, err := os.Stat(m.path); os.IsNotExist(err) { - if err := os.WriteFile(m.path, []byte{}, 0644); err != nil { + if err := os.WriteFile(m.path, []byte{}, 0o644); err != nil { return nil, 0, syscall.EIO } } - file, err := os.OpenFile(m.path, int(flags), 0644) + file, err := os.OpenFile(m.path, int(flags), 0o644) if err != nil { return nil, 0, syscall.EIO } @@ -135,6 +135,7 @@ func (m *MusicAppMetadataFile) Unlink(ctx context.Context, name string) syscall. if err := os.Remove(m.path); err != nil { return syscall.ENOENT } + return 0 } diff --git a/internal/domains/filesystem/music_directory.go b/internal/domains/filesystem/music_directory.go index 5c849b0..3d00a13 100644 --- a/internal/domains/filesystem/music_directory.go +++ b/internal/domains/filesystem/music_directory.go @@ -34,7 +34,7 @@ var ( ) func (d *MusicDir) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { - out.Mode = fuse.S_IFDIR | 0755 + out.Mode = fuse.S_IFDIR | 0o755 out.Nlink = 2 // Minimum . and .. out.Ino = d.StableAttr().Ino out.Size = 4096 @@ -77,6 +77,7 @@ func (d *MusicDir) Getxattr(ctx context.Context, attr string, dest []byte) (uint if len(dest) > 0 { return 0, 0 } + return 0, 0 default: return 0, syscall.ENODATA @@ -113,7 +114,7 @@ func (d *MusicDir) Create(ctx context.Context, name string, flags uint32, mode u }, ) - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = ch.StableAttr().Ino out.Size = 0 @@ -144,7 +145,7 @@ func (d *MusicDir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) }, ) - out.Mode = fuse.S_IFREG | 0444 + out.Mode = fuse.S_IFREG | 0o444 out.Nlink = 1 out.Ino = ch.StableAttr().Ino @@ -165,6 +166,7 @@ func (d *MusicDir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) // Check real file or directory fullPath := filepath.Join(d.path, name) + info, err := os.Stat(fullPath) if err != nil { return nil, syscall.ENOENT @@ -179,7 +181,7 @@ func (d *MusicDir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) }, ) - out.Mode = fuse.S_IFDIR | 0755 + out.Mode = fuse.S_IFDIR | 0o755 out.Nlink = 2 out.Ino = ch.StableAttr().Ino out.Size = 4096 @@ -201,9 +203,9 @@ func (d *MusicDir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) ) if isMeta { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 } else { - out.Mode = fuse.S_IFREG | 0444 + out.Mode = fuse.S_IFREG | 0o444 } out.Nlink = 1 @@ -224,12 +226,12 @@ func (d *MusicDir) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { dirEntries = append(dirEntries, fuse.DirEntry{ Name: ".", - Mode: fuse.S_IFDIR | 0755, + Mode: fuse.S_IFDIR | 0o755, Ino: d.StableAttr().Ino, }) dirEntries = append(dirEntries, fuse.DirEntry{ Name: "..", - Mode: fuse.S_IFDIR | 0755, + Mode: fuse.S_IFDIR | 0o755, Ino: 1, // Parent (root) inode }) @@ -249,19 +251,19 @@ func (d *MusicDir) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { continue } - mode := fuse.S_IFREG | 0444 + mode := fuse.S_IFREG | 0o444 if entry.IsDir() { - mode = fuse.S_IFDIR | 0755 + mode = fuse.S_IFDIR | 0o755 } // Convert .flac to .m4a in directory listing if strings.HasSuffix(strings.ToLower(name), ".flac") { name = name[:len(name)-5] + ".m4a" if !d.f.isiTunesMetadata(name) { - mode = fuse.S_IFREG | 0644 + mode = fuse.S_IFREG | 0o644 } } else if !d.f.isiTunesMetadata(name) { - mode = fuse.S_IFREG | 0644 + mode = fuse.S_IFREG | 0o644 } dirEntries = append(dirEntries, fuse.DirEntry{ diff --git a/internal/domains/filesystem/music_file.go b/internal/domains/filesystem/music_file.go index 21d05eb..d9b9337 100644 --- a/internal/domains/filesystem/music_file.go +++ b/internal/domains/filesystem/music_file.go @@ -31,7 +31,7 @@ func (f *MusicFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.Att metaPath := filepath.Join(f.f.metadataDir, f.virtualName) if info, err := os.Stat(metaPath); err == nil { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = f.StableAttr().Ino out.Size = uint64(info.Size()) @@ -40,7 +40,7 @@ func (f *MusicFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.Att out.Ctime = out.Mtime out.Blocks = (out.Size + 511) / 512 } else { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = f.StableAttr().Ino out.Size = 0 @@ -53,7 +53,7 @@ func (f *MusicFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.Att return 0 } - out.Mode = fuse.S_IFREG | 0444 + out.Mode = fuse.S_IFREG | 0o444 out.Nlink = 1 out.Ino = f.StableAttr().Ino out.Blocks = 1 @@ -76,7 +76,7 @@ func (f *MusicFile) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetA if f.isMetaFile { metaPath := filepath.Join(f.f.metadataDir, f.virtualName) if info, err := os.Stat(metaPath); err == nil { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = f.StableAttr().Ino out.Size = uint64(info.Size()) @@ -85,7 +85,7 @@ func (f *MusicFile) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetA out.Ctime = out.Mtime out.Blocks = (out.Size + 511) / 512 } else { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = f.StableAttr().Ino out.Size = 0 @@ -105,10 +105,11 @@ func (f *MusicFile) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, f if f.isMetaFile { metaPath := filepath.Join(f.f.metadataDir, f.virtualName) - file, err := os.OpenFile(metaPath, int(flags), 0644) + file, err := os.OpenFile(metaPath, int(flags), 0o644) if err != nil && os.IsNotExist(err) { file, err = os.Create(metaPath) } + if err != nil { return nil, 0, syscall.EIO } diff --git a/internal/domains/filesystem/root.go b/internal/domains/filesystem/root.go index 7195989..6e9fafa 100644 --- a/internal/domains/filesystem/root.go +++ b/internal/domains/filesystem/root.go @@ -50,7 +50,7 @@ func (r *RootDirectory) Create( }, ) - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = ch.StableAttr().Ino out.Size = 0 @@ -77,7 +77,7 @@ func (r *RootDirectory) Lookup(ctx context.Context, name string, out *fuse.Entry }, ) - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 out.Nlink = 1 out.Ino = ch.StableAttr().Ino @@ -114,7 +114,7 @@ func (r *RootDirectory) Lookup(ctx context.Context, name string, out *fuse.Entry }, ) - out.Mode = fuse.S_IFREG | 0444 + out.Mode = fuse.S_IFREG | 0o444 out.Nlink = 1 out.Ino = ch.StableAttr().Ino @@ -135,6 +135,7 @@ func (r *RootDirectory) Lookup(ctx context.Context, name string, out *fuse.Entry // Check real file or directory fullPath := filepath.Join(r.f.sourceDir, name) + info, err := os.Stat(fullPath) if err != nil { return nil, syscall.ENOENT @@ -146,7 +147,7 @@ func (r *RootDirectory) Lookup(ctx context.Context, name string, out *fuse.Entry Ino: r.f.nextInode(), }) - out.Mode = fuse.S_IFDIR | 0755 + out.Mode = fuse.S_IFDIR | 0o755 out.Nlink = 2 // Minimum . and .. out.Ino = ch.StableAttr().Ino out.Size = 4096 @@ -166,9 +167,9 @@ func (r *RootDirectory) Lookup(ctx context.Context, name string, out *fuse.Entry }) if isMeta { - out.Mode = fuse.S_IFREG | 0644 + out.Mode = fuse.S_IFREG | 0o644 } else { - out.Mode = fuse.S_IFREG | 0444 + out.Mode = fuse.S_IFREG | 0o444 } out.Nlink = 1 @@ -190,12 +191,12 @@ func (r *RootDirectory) Readdir(ctx context.Context) (fs.DirStream, syscall.Errn // Always include . and .. first dirEntries = append(dirEntries, fuse.DirEntry{ Name: ".", - Mode: fuse.S_IFDIR | 0755, + Mode: fuse.S_IFDIR | 0o755, Ino: 1, // Root inode }) dirEntries = append(dirEntries, fuse.DirEntry{ Name: "..", - Mode: fuse.S_IFDIR | 0755, + Mode: fuse.S_IFDIR | 0o755, Ino: 1, }) @@ -205,6 +206,7 @@ func (r *RootDirectory) Readdir(ctx context.Context) (fs.DirStream, syscall.Errn r.f.app.Logger().WithError(err).WithField("path", r.f.sourceDir).Error( "Error reading directory", ) + return fs.NewListDirStream(dirEntries), 0 } @@ -215,9 +217,9 @@ func (r *RootDirectory) Readdir(ctx context.Context) (fs.DirStream, syscall.Errn continue } - mode := fuse.S_IFREG | 0444 + mode := fuse.S_IFREG | 0o444 if entry.IsDir() { - mode = fuse.S_IFDIR | 0755 + mode = fuse.S_IFDIR | 0o755 } // Convert .flac to .m4a in directory listing @@ -225,7 +227,7 @@ func (r *RootDirectory) Readdir(ctx context.Context) (fs.DirStream, syscall.Errn name = name[:len(name)-5] + ".m4a" } - mode = fuse.S_IFREG | 0644 + mode = fuse.S_IFREG | 0o644 dirEntries = append(dirEntries, fuse.DirEntry{ Name: name, @@ -246,7 +248,7 @@ func (r *RootDirectory) Getattr( ctx context.Context, f fs.FileHandle, out *fuse.AttrOut, ) syscall.Errno { // Set basic directory attributes - out.Mode = fuse.S_IFDIR | 0755 + out.Mode = fuse.S_IFDIR | 0o755 // Set nlink to at least 2 (for . and ..) out.Nlink = 2 diff --git a/internal/domains/transcoder/convert.go b/internal/domains/transcoder/convert.go index 2dc4c06..26837c9 100644 --- a/internal/domains/transcoder/convert.go +++ b/internal/domains/transcoder/convert.go @@ -52,11 +52,11 @@ func (t *Transcoder) Convert(sourcePath, destinationPath string) (int64, error) analyzeOutput, err := sourceAnalyzeCmd.Output() if err == nil { - // Investiage bit depth and sample rate from ffprobe output. + // Investigate bit depth and sample rate from ffprobe output. // We need that to make sure we don't oversample files that are lower // than the default sample rate and bit depth. - lines := strings.Split(strings.TrimSpace(string(analyzeOutput)), "\n") - for _, line := range lines { + lines := strings.SplitSeq(strings.TrimSpace(string(analyzeOutput)), "\n") + for line := range lines { if strings.Contains(line, "audio") { parts := strings.Split(line, ",") if len(parts) >= 6 { @@ -137,7 +137,7 @@ func (t *Transcoder) Convert(sourcePath, destinationPath string) (int64, error) "-af", "aresample=48000:resampler=soxr:precision=28", ) } else { - ffmpegArgs = append(ffmpegArgs, "-ar", fmt.Sprintf("%d", sampleRate)) + ffmpegArgs = append(ffmpegArgs, "-ar", strconv.Itoa(sampleRate)) } if needsBitReduce { @@ -151,7 +151,7 @@ func (t *Transcoder) Convert(sourcePath, destinationPath string) (int64, error) // Handle metadata copying and sort_artist filling ffmpegArgs = append(ffmpegArgs, "-map_metadata", "0", - "-metadata", fmt.Sprintf("sort_artist=%s", t.escapeMetadata(sortArtist)), + "-metadata", "sort_artist="+t.escapeMetadata(sortArtist), "-write_id3v2", "1", "-id3v2_version", "3", destinationPath, @@ -165,7 +165,9 @@ func (t *Transcoder) Convert(sourcePath, destinationPath string) (int64, error) ).Debug("FFMpeg parameters") ffmpeg := exec.Command("ffmpeg", ffmpegArgs...) + var stderr bytes.Buffer + ffmpeg.Stderr = &stderr if err := ffmpeg.Run(); err != nil {