2020-02-01 17:58:45 +01:00
package scanner
import (
"container/list"
"fmt"
"io/ioutil"
"log"
"os"
"path"
2020-12-17 22:51:43 +01:00
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/graphql/notification"
"github.com/photoview/photoview/api/utils"
2020-06-22 23:52:41 +02:00
"github.com/pkg/errors"
2020-11-23 19:39:44 +01:00
"gorm.io/gorm"
2020-02-01 17:58:45 +01:00
)
2020-11-23 19:39:44 +01:00
func findAlbumsForUser ( db * gorm . DB , user * models . User , album_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
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 ,
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
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
}
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 ,
}
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
}
}
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
}
2020-06-22 23:52:41 +02:00
if item . IsDir ( ) && directoryContainsPhotos ( subalbumPath , album_cache ) {
2020-02-01 17:58:45 +01:00
scanQueue . PushBack ( scanInfo {
2020-12-22 01:14:43 +01:00
path : subalbumPath ,
parent : album ,
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
}
2020-06-22 23:52:41 +02:00
func directoryContainsPhotos ( rootPath string , cache * AlbumScannerCache ) 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 )
2020-02-01 17:58:45 +01:00
dirContent , err := ioutil . ReadDir ( dirPath )
if err != nil {
2020-02-23 11:59:57 +01:00
ScannerError ( "Could not read directory: %s\n" , err . Error ( ) )
2020-02-01 17:58:45 +01:00
return false
}
for _ , fileInfo := range dirContent {
filePath := path . Join ( dirPath , fileInfo . Name ( ) )
if fileInfo . IsDir ( ) {
scanQueue . PushBack ( filePath )
} else {
2020-07-11 14:05:06 +02:00
if isPathMedia ( filePath , cache ) {
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 {
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
}
2020-02-12 18:10:52 +01:00
2020-02-23 11:59:57 +01:00
func ScannerError ( format string , args ... interface { } ) {
message := fmt . Sprintf ( format , args ... )
log . Printf ( "ERROR: %s" , message )
notification . BroadcastNotification ( & models . Notification {
Key : utils . GenerateToken ( ) ,
Type : models . NotificationTypeMessage ,
Header : "Scanner error" ,
Content : message ,
Negative : true ,
} )
}
2020-02-23 12:43:45 +01:00
2021-01-17 16:50:48 +01:00
// MediaCachePath returns the path for where the media cache is located on the file system
func MediaCachePath ( ) string {
photoCache := utils . EnvMediaCachePath . GetValue ( )
2020-02-23 12:43:45 +01:00
if photoCache == "" {
2021-01-17 16:50:48 +01:00
photoCache = "./media_cache"
2020-02-23 12:43:45 +01:00
}
return photoCache
}