|
|
@@ -8,8 +8,11 @@ import (
|
|
|
"github.com/felamaslen/go-music-player/pkg/database"
|
|
|
"github.com/felamaslen/go-music-player/pkg/logger"
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
+ "github.com/lib/pq"
|
|
|
)
|
|
|
|
|
|
+const BATCH_SIZE = 100
|
|
|
+
|
|
|
func ReadMultipleFiles(basePath string, files chan *File) chan *Song {
|
|
|
var l = logger.CreateLogger(config.GetConfig().LogLevel)
|
|
|
|
|
|
@@ -51,19 +54,26 @@ func isValidFile(file string) bool {
|
|
|
func recursiveDirScan(
|
|
|
db *sqlx.DB,
|
|
|
l *logger.Logger,
|
|
|
- output *chan *File,
|
|
|
+ inputBatch *[BATCH_SIZE]*File,
|
|
|
+ fileBatches *chan [BATCH_SIZE]*File,
|
|
|
rootDirectory string,
|
|
|
relativePath string,
|
|
|
isRoot bool,
|
|
|
-) {
|
|
|
+ batchIndex int,
|
|
|
+) int {
|
|
|
directoryToScan := filepath.Join(rootDirectory, relativePath)
|
|
|
|
|
|
if (isRoot) {
|
|
|
l.Verbose("Scanning root directory: %s\n", directoryToScan)
|
|
|
|
|
|
defer func() {
|
|
|
+ var remainingItemsExist = batchIndex > 0
|
|
|
+ if remainingItemsExist {
|
|
|
+ *fileBatches <- *inputBatch
|
|
|
+ }
|
|
|
+
|
|
|
l.Verbose("Finished recursive directory scan")
|
|
|
- close(*output)
|
|
|
+ close(*fileBatches)
|
|
|
}()
|
|
|
} else {
|
|
|
l.Debug("Scanning subdirectory: %s\n", directoryToScan)
|
|
|
@@ -73,45 +83,103 @@ func recursiveDirScan(
|
|
|
|
|
|
if err != nil {
|
|
|
l.Fatal("Error scanning directory: (%s): %s", directoryToScan, err)
|
|
|
- return
|
|
|
}
|
|
|
|
|
|
for _, file := range(files) {
|
|
|
fileRelativePath := filepath.Join(relativePath, file.Name())
|
|
|
|
|
|
if file.IsDir() {
|
|
|
- recursiveDirScan(
|
|
|
+ batchIndex = recursiveDirScan(
|
|
|
db,
|
|
|
l,
|
|
|
- output,
|
|
|
+ inputBatch,
|
|
|
+ fileBatches,
|
|
|
rootDirectory,
|
|
|
fileRelativePath,
|
|
|
false,
|
|
|
+ batchIndex,
|
|
|
)
|
|
|
} else if isValidFile(file.Name()) {
|
|
|
- modifiedDate := file.ModTime().Unix()
|
|
|
|
|
|
- var existingCount = 0
|
|
|
+ if batchIndex == BATCH_SIZE {
|
|
|
+ *fileBatches <- *inputBatch
|
|
|
+ batchIndex = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ (*inputBatch)[batchIndex] = &File{
|
|
|
+ RelativePath: fileRelativePath,
|
|
|
+ ModifiedDate: file.ModTime().Unix(),
|
|
|
+ }
|
|
|
+
|
|
|
+ batchIndex++
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return batchIndex
|
|
|
+}
|
|
|
+
|
|
|
+func maybeReadFile(
|
|
|
+ db *sqlx.DB,
|
|
|
+ l *logger.Logger,
|
|
|
+ output *chan *File,
|
|
|
+ inputBatch *chan [BATCH_SIZE]*File,
|
|
|
+ basePath string,
|
|
|
+) {
|
|
|
+ defer close(*output)
|
|
|
+
|
|
|
+ for {
|
|
|
+ select {
|
|
|
+ case batch, more := <- *inputBatch:
|
|
|
+ if !more {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ var relativePaths pq.StringArray
|
|
|
+ var modifiedDates pq.Int64Array
|
|
|
+
|
|
|
+ for _, s := range batch {
|
|
|
+ if s == nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
|
|
|
- err := db.Get(
|
|
|
- &existingCount,
|
|
|
+ relativePaths = append(relativePaths, s.RelativePath)
|
|
|
+ modifiedDates = append(modifiedDates, s.ModifiedDate)
|
|
|
+ }
|
|
|
+
|
|
|
+ newOrUpdatedFiles, err := db.Queryx(
|
|
|
`
|
|
|
- select count(*) from songs
|
|
|
- where base_path = $1 and relative_path = $2 and modified_date = $3
|
|
|
+ select r.relative_path, r.modified_date
|
|
|
+ from (
|
|
|
+ select * from unnest($1::varchar[], $2::bigint[])
|
|
|
+ as t(relative_path, modified_date)
|
|
|
+ ) r
|
|
|
+
|
|
|
+ left join songs on
|
|
|
+ songs.base_path = $3
|
|
|
+ and songs.relative_path = r.relative_path
|
|
|
+ and songs.modified_date = r.modified_date
|
|
|
+
|
|
|
+ where songs.id is null
|
|
|
`,
|
|
|
- rootDirectory,
|
|
|
- fileRelativePath,
|
|
|
- modifiedDate,
|
|
|
+ relativePaths,
|
|
|
+ modifiedDates,
|
|
|
+ basePath,
|
|
|
)
|
|
|
|
|
|
- if err == nil && existingCount == 0 {
|
|
|
- l.Verbose("Found file: %s\n", fileRelativePath)
|
|
|
+ if err != nil {
|
|
|
+ l.Fatal("Error determining file eligibility: %v\n", err)
|
|
|
+ }
|
|
|
|
|
|
- *output <- &File{
|
|
|
- RelativePath: fileRelativePath,
|
|
|
- ModifiedDate: modifiedDate,
|
|
|
- }
|
|
|
+ for newOrUpdatedFiles.Next() {
|
|
|
+ var file File
|
|
|
+ newOrUpdatedFiles.StructScan(&file)
|
|
|
+
|
|
|
+ l.Verbose("New or updated file: %s\n", file.RelativePath)
|
|
|
+
|
|
|
+ *output <- &file
|
|
|
}
|
|
|
+
|
|
|
+ newOrUpdatedFiles.Close()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -120,18 +188,27 @@ func ScanDirectory(directory string) chan *File {
|
|
|
db := database.GetConnection()
|
|
|
l := logger.CreateLogger(config.GetConfig().LogLevel)
|
|
|
|
|
|
- files := make(chan *File)
|
|
|
+ output := make(chan *File)
|
|
|
+ fileBatches := make(chan [BATCH_SIZE]*File)
|
|
|
+
|
|
|
+ go func() {
|
|
|
+ maybeReadFile(db, l, &output, &fileBatches, directory)
|
|
|
+ }()
|
|
|
+
|
|
|
+ var inputBatch [BATCH_SIZE]*File
|
|
|
|
|
|
go func() {
|
|
|
recursiveDirScan(
|
|
|
db,
|
|
|
l,
|
|
|
- &files,
|
|
|
+ &inputBatch,
|
|
|
+ &fileBatches,
|
|
|
directory,
|
|
|
"",
|
|
|
true,
|
|
|
+ 0,
|
|
|
)
|
|
|
}()
|
|
|
|
|
|
- return files
|
|
|
+ return output
|
|
|
}
|