2020-02-01 17:58:45 +01:00
package scanner
import (
2021-02-23 15:59:37 +01:00
"bufio"
2020-02-01 17:58:45 +01:00
"container/list"
"io/ioutil"
"log"
"os"
"path"
2020-12-17 22:51:43 +01:00
"github.com/photoview/photoview/api/graphql/models"
2021-05-06 21:54:31 +02:00
"github.com/photoview/photoview/api/scanner/scanner_cache"
"github.com/photoview/photoview/api/scanner/scanner_utils"
2021-07-15 16:16:05 +02:00
"github.com/photoview/photoview/api/utils"
2020-06-22 23:52:41 +02:00
"github.com/pkg/errors"
2021-04-11 22:55:44 +02:00
ignore "github.com/sabhiram/go-gitignore"
2020-11-23 19:39:44 +01:00
"gorm.io/gorm"
2020-02-01 17:58:45 +01:00
)
2021-04-03 17:49:18 +02:00
func getPhotoviewIgnore ( ignorePath string ) ( [ ] string , error ) {
2021-02-23 15:59:37 +01:00
var photoviewIgnore [ ] string
// Open .photoviewignore file, if exists
photoviewIgnoreFile , err := os . Open ( path . Join ( ignorePath , ".photoviewignore" ) )
if err != nil {
2021-11-06 12:23:47 +01:00
if os . IsNotExist ( err ) {
2021-04-11 22:55:44 +02:00
return photoviewIgnore , nil
}
2021-02-23 15:59:37 +01:00
return photoviewIgnore , err
}
// Close file on exit
defer photoviewIgnoreFile . Close ( )
// Read and save .photoviewignore data
2021-04-03 17:49:18 +02:00
scanner := bufio . NewScanner ( photoviewIgnoreFile )
for scanner . Scan ( ) {
2021-02-23 15:59:37 +01:00
photoviewIgnore = append ( photoviewIgnore , scanner . Text ( ) )
log . Printf ( "Ignore found: %s" , scanner . Text ( ) )
}
2021-04-03 17:49:18 +02:00
return photoviewIgnore , scanner . Err ( )
2021-02-23 15:59:37 +01:00
}
2021-05-06 21:54:31 +02:00
func findAlbumsForUser ( db * gorm . DB , user * models . User , album_cache * scanner_cache . AlbumScannerCache ) ( [ ] * models . Album , [ ] error ) {
2020-02-21 20:51:50 +01:00
2020-12-22 01:14:43 +01:00
if err := user . FillAlbums ( db ) ; err != nil {
return nil , [ ] error { err }
}
userAlbumIDs := make ( [ ] int , len ( user . Albums ) )
for i , album := range user . Albums {
userAlbumIDs [ i ] = album . ID
}
2020-12-20 17:10:00 +01:00
var userRootAlbums [ ] * models . Album
2021-01-26 15:30:21 +01:00
if err := db . Where ( "id IN (?)" , userAlbumIDs ) . Where ( "parent_album_id IS NULL OR parent_album_id NOT IN (?)" , userAlbumIDs ) . Find ( & userRootAlbums ) . Error ; err != nil {
2020-12-22 01:14:43 +01:00
return nil , [ ] error { err }
2020-04-16 11:23:34 +02:00
}
2020-12-20 17:10:00 +01:00
scanErrors := make ( [ ] error , 0 )
2020-02-01 17:58:45 +01:00
type scanInfo struct {
2020-12-22 01:14:43 +01:00
path string
parent * models . Album
2021-02-23 15:59:37 +01:00
ignore [ ] string
2020-02-01 17:58:45 +01:00
}
scanQueue := list . New ( )
2020-12-20 17:10:00 +01:00
for _ , album := range userRootAlbums {
// Check if user album directory exists on the file system
if _ , err := os . Stat ( album . Path ) ; err != nil {
if os . IsNotExist ( err ) {
scanErrors = append ( scanErrors , errors . Errorf ( "Album directory for user '%s' does not exist '%s'\n" , user . Username , album . Path ) )
} else {
scanErrors = append ( scanErrors , errors . Errorf ( "Could not read album directory for user '%s': %s\n" , user . Username , album . Path ) )
}
} else {
scanQueue . PushBack ( scanInfo {
2020-12-22 01:14:43 +01:00
path : album . Path ,
parent : nil ,
2021-02-23 15:59:37 +01:00
ignore : nil ,
2020-12-20 17:10:00 +01:00
} )
}
}
2020-02-01 17:58:45 +01:00
2020-06-22 23:52:41 +02:00
userAlbums := make ( [ ] * models . Album , 0 )
2020-02-26 21:23:13 +01:00
2020-02-01 17:58:45 +01:00
for scanQueue . Front ( ) != nil {
albumInfo := scanQueue . Front ( ) . Value . ( scanInfo )
scanQueue . Remove ( scanQueue . Front ( ) )
albumPath := albumInfo . path
2020-12-22 01:14:43 +01:00
albumParent := albumInfo . parent
2021-02-23 15:59:37 +01:00
albumIgnore := albumInfo . ignore
2020-02-01 17:58:45 +01:00
// Read path
dirContent , err := ioutil . ReadDir ( albumPath )
if err != nil {
2020-12-20 17:10:00 +01:00
scanErrors = append ( scanErrors , errors . Wrapf ( err , "read directory (%s)" , albumPath ) )
2020-02-23 11:59:57 +01:00
continue
2020-02-01 17:58:45 +01:00
}
2021-02-23 15:59:37 +01:00
// Skip this dir if in ignore list
ignorePaths := ignore . CompileIgnoreLines ( albumIgnore ... )
2021-04-03 17:49:18 +02:00
if ignorePaths . MatchesPath ( albumPath + "/" ) {
2021-02-23 15:59:37 +01:00
log . Printf ( "Skip, directroy %s is in ignore file" , albumPath )
continue
}
// Update ignore dir list
photoviewIgnore , err := getPhotoviewIgnore ( albumPath )
if err != nil {
log . Printf ( "Failed to get ignore file, err = %s" , err )
} else {
albumIgnore = append ( albumIgnore , photoviewIgnore ... )
}
2020-11-25 23:06:47 +01:00
// Will become new album or album from db
2020-12-22 01:14:43 +01:00
var album * models . Album
2020-02-01 17:58:45 +01:00
2020-11-25 23:06:47 +01:00
transErr := db . Transaction ( func ( tx * gorm . DB ) error {
log . Printf ( "Scanning directory: %s" , albumPath )
2020-02-01 17:58:45 +01:00
2020-12-22 01:14:43 +01:00
// check if album already exists
var albumResult [ ] models . Album
2021-01-17 12:45:23 +01:00
result := tx . Where ( "path_hash = ?" , models . MD5Hash ( albumPath ) ) . Find ( & albumResult )
2020-12-22 01:14:43 +01:00
if result . Error != nil {
return result . Error
}
2020-11-25 23:06:47 +01:00
2020-12-22 01:14:43 +01:00
// album does not exist, create new
if len ( albumResult ) == 0 {
albumTitle := path . Base ( albumPath )
var albumParentID * int
parentOwners := make ( [ ] models . User , 0 )
if albumParent != nil {
albumParentID = & albumParent . ID
2021-01-26 15:30:21 +01:00
if err := tx . Model ( & albumParent ) . Association ( "Owners" ) . Find ( & parentOwners ) ; err != nil {
2020-12-22 01:14:43 +01:00
return err
}
}
album = & models . Album {
Title : albumTitle ,
ParentAlbumID : albumParentID ,
Path : albumPath ,
}
2021-03-10 21:51:36 +01:00
// Store album ignore
album_cache . InsertAlbumIgnore ( albumPath , albumIgnore )
2020-12-22 01:14:43 +01:00
if err := tx . Create ( & album ) . Error ; err != nil {
return errors . Wrap ( err , "insert album into database" )
}
if err := tx . Model ( & album ) . Association ( "Owners" ) . Append ( parentOwners ) ; err != nil {
return errors . Wrap ( err , "add owners to album" )
}
} else {
album = & albumResult [ 0 ]
2021-01-26 15:30:21 +01:00
// Add user as an owner of the album if not already
var userAlbumOwner [ ] models . User
if err := tx . Model ( & album ) . Association ( "Owners" ) . Find ( & userAlbumOwner , "user_albums.user_id = ?" , user . ID ) ; err != nil {
return err
}
if len ( userAlbumOwner ) == 0 {
newUser := models . User { }
newUser . ID = user . ID
if err := tx . Model ( & album ) . Association ( "Owners" ) . Append ( & newUser ) ; err != nil {
return err
}
}
2021-03-10 21:51:36 +01:00
// Update album ignore
album_cache . InsertAlbumIgnore ( albumPath , albumIgnore )
2020-11-25 23:06:47 +01:00
}
2020-12-22 01:14:43 +01:00
userAlbums = append ( userAlbums , album )
2020-11-25 23:06:47 +01:00
return nil
} )
2020-02-01 17:58:45 +01:00
2020-11-25 23:06:47 +01:00
if transErr != nil {
2020-12-20 17:10:00 +01:00
scanErrors = append ( scanErrors , errors . Wrap ( transErr , "begin database transaction" ) )
2020-06-22 23:52:41 +02:00
continue
2020-02-01 17:58:45 +01:00
}
// Scan for sub-albums
for _ , item := range dirContent {
subalbumPath := path . Join ( albumPath , item . Name ( ) )
2020-03-07 15:34:32 +01:00
// Skip if directory is hidden
if path . Base ( subalbumPath ) [ 0 : 1 ] == "." {
continue
}
2021-07-15 16:16:05 +02:00
isDirSymlink , err := utils . IsDirSymlink ( subalbumPath )
if err != nil {
scanErrors = append ( scanErrors , errors . Wrapf ( err , "could not check for symlink target of %s" , subalbumPath ) )
continue
}
if ( item . IsDir ( ) || isDirSymlink ) && directoryContainsPhotos ( subalbumPath , album_cache , albumIgnore ) {
2020-02-01 17:58:45 +01:00
scanQueue . PushBack ( scanInfo {
2020-12-22 01:14:43 +01:00
path : subalbumPath ,
parent : album ,
2021-02-23 15:59:37 +01:00
ignore : albumIgnore ,
2020-02-01 17:58:45 +01:00
} )
}
}
}
2020-06-22 23:52:41 +02:00
deleteErrors := deleteOldUserAlbums ( db , userAlbums , user )
2020-12-20 17:10:00 +01:00
scanErrors = append ( scanErrors , deleteErrors ... )
2020-02-26 19:44:47 +01:00
2020-12-20 17:10:00 +01:00
return userAlbums , scanErrors
2020-02-01 17:58:45 +01:00
}
2021-05-06 21:54:31 +02:00
func directoryContainsPhotos ( rootPath string , cache * scanner_cache . AlbumScannerCache , albumIgnore [ ] string ) bool {
2020-02-12 18:45:58 +01:00
2020-06-22 23:52:41 +02:00
if contains_image := cache . AlbumContainsPhotos ( rootPath ) ; contains_image != nil {
2020-02-12 18:45:58 +01:00
return * contains_image
}
2020-02-01 17:58:45 +01:00
scanQueue := list . New ( )
scanQueue . PushBack ( rootPath )
2020-02-12 18:45:58 +01:00
scanned_directories := make ( [ ] string , 0 )
2020-02-01 17:58:45 +01:00
for scanQueue . Front ( ) != nil {
dirPath := scanQueue . Front ( ) . Value . ( string )
scanQueue . Remove ( scanQueue . Front ( ) )
2020-02-12 18:45:58 +01:00
scanned_directories = append ( scanned_directories , dirPath )
2021-03-10 21:51:36 +01:00
// Update ignore dir list
photoviewIgnore , err := getPhotoviewIgnore ( dirPath )
if err != nil {
log . Printf ( "Failed to get ignore file, err = %s" , err )
} else {
albumIgnore = append ( albumIgnore , photoviewIgnore ... )
}
ignoreEntries := ignore . CompileIgnoreLines ( albumIgnore ... )
2020-02-01 17:58:45 +01:00
dirContent , err := ioutil . ReadDir ( dirPath )
if err != nil {
2021-05-06 21:54:31 +02:00
scanner_utils . ScannerError ( "Could not read directory (%s): %s\n" , dirPath , err . Error ( ) )
2020-02-01 17:58:45 +01:00
return false
}
for _ , fileInfo := range dirContent {
filePath := path . Join ( dirPath , fileInfo . Name ( ) )
2021-07-20 20:39:11 +02:00
isDirSymlink , err := utils . IsDirSymlink ( filePath )
if err != nil {
log . Printf ( "Cannot detect whether %s is symlink to a directory. Pretending it is not" , filePath )
isDirSymlink = false
}
if fileInfo . IsDir ( ) || isDirSymlink {
2020-02-01 17:58:45 +01:00
scanQueue . PushBack ( filePath )
} else {
2021-05-06 21:54:31 +02:00
if cache . IsPathMedia ( filePath ) {
2021-03-10 21:51:36 +01:00
if ignoreEntries . MatchesPath ( fileInfo . Name ( ) ) {
log . Printf ( "Match found %s, continue search for media" , fileInfo . Name ( ) )
continue
}
log . Printf ( "Insert Album %s %s, contains photo is true" , dirPath , rootPath )
2020-06-22 23:52:41 +02:00
cache . InsertAlbumPaths ( dirPath , rootPath , true )
2020-02-01 17:58:45 +01:00
return true
}
}
}
}
2020-02-12 18:45:58 +01:00
for _ , scanned_path := range scanned_directories {
2021-03-10 21:51:36 +01:00
log . Printf ( "Insert Album %s, contains photo is false" , scanned_path )
2020-06-22 23:52:41 +02:00
cache . InsertAlbumPath ( scanned_path , false )
2020-02-12 18:45:58 +01:00
}
2020-02-01 17:58:45 +01:00
return false
}