github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/googlephotos/albums.go (about)

     1  // This file contains the albums abstraction
     2  
     3  package googlephotos
     4  
     5  import (
     6  	"path"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/rclone/rclone/backend/googlephotos/api"
    11  )
    12  
    13  // All the albums
    14  type albums struct {
    15  	mu      sync.Mutex
    16  	dupes   map[string][]*api.Album // duplicated names
    17  	byID    map[string]*api.Album   //..indexed by ID
    18  	byTitle map[string]*api.Album   //..indexed by Title
    19  	path    map[string][]string     // partial album names to directory
    20  }
    21  
    22  // Create a new album
    23  func newAlbums() *albums {
    24  	return &albums{
    25  		dupes:   map[string][]*api.Album{},
    26  		byID:    map[string]*api.Album{},
    27  		byTitle: map[string]*api.Album{},
    28  		path:    map[string][]string{},
    29  	}
    30  }
    31  
    32  // add an album
    33  func (as *albums) add(album *api.Album) {
    34  	// Munge the name of the album into a sensible path name
    35  	album.Title = path.Clean(album.Title)
    36  	if album.Title == "." || album.Title == "/" {
    37  		album.Title = addID("", album.ID)
    38  	}
    39  
    40  	as.mu.Lock()
    41  	as._add(album)
    42  	as.mu.Unlock()
    43  }
    44  
    45  // _add an album - call with lock held
    46  func (as *albums) _add(album *api.Album) {
    47  	// update dupes by title
    48  	dupes := as.dupes[album.Title]
    49  	dupes = append(dupes, album)
    50  	as.dupes[album.Title] = dupes
    51  
    52  	// Dedupe the album name if necessary
    53  	if len(dupes) >= 2 {
    54  		// If this is the first dupe, then need to adjust the first one
    55  		if len(dupes) == 2 {
    56  			firstAlbum := dupes[0]
    57  			as._del(firstAlbum)
    58  			as._add(firstAlbum)
    59  			// undo add of firstAlbum to dupes
    60  			as.dupes[album.Title] = dupes
    61  		}
    62  		album.Title = addID(album.Title, album.ID)
    63  	}
    64  
    65  	// Store the new album
    66  	as.byID[album.ID] = album
    67  	as.byTitle[album.Title] = album
    68  
    69  	// Store the partial paths
    70  	dir, leaf := album.Title, ""
    71  	for dir != "" {
    72  		i := strings.LastIndex(dir, "/")
    73  		if i >= 0 {
    74  			dir, leaf = dir[:i], dir[i+1:]
    75  		} else {
    76  			dir, leaf = "", dir
    77  		}
    78  		dirs := as.path[dir]
    79  		found := false
    80  		for _, dir := range dirs {
    81  			if dir == leaf {
    82  				found = true
    83  			}
    84  		}
    85  		if !found {
    86  			as.path[dir] = append(as.path[dir], leaf)
    87  		}
    88  	}
    89  }
    90  
    91  // del an album
    92  func (as *albums) del(album *api.Album) {
    93  	as.mu.Lock()
    94  	as._del(album)
    95  	as.mu.Unlock()
    96  }
    97  
    98  // _del an album - call with lock held
    99  func (as *albums) _del(album *api.Album) {
   100  	// We leave in dupes so it doesn't cause albums to get renamed
   101  
   102  	// Remove from byID and byTitle
   103  	delete(as.byID, album.ID)
   104  	delete(as.byTitle, album.Title)
   105  
   106  	// Remove from paths
   107  	dir, leaf := album.Title, ""
   108  	for dir != "" {
   109  		// Can't delete if this dir exists anywhere in the path structure
   110  		if _, found := as.path[dir]; found {
   111  			break
   112  		}
   113  		i := strings.LastIndex(dir, "/")
   114  		if i >= 0 {
   115  			dir, leaf = dir[:i], dir[i+1:]
   116  		} else {
   117  			dir, leaf = "", dir
   118  		}
   119  		dirs := as.path[dir]
   120  		for i, dir := range dirs {
   121  			if dir == leaf {
   122  				dirs = append(dirs[:i], dirs[i+1:]...)
   123  				break
   124  			}
   125  		}
   126  		if len(dirs) == 0 {
   127  			delete(as.path, dir)
   128  		} else {
   129  			as.path[dir] = dirs
   130  		}
   131  	}
   132  }
   133  
   134  // get an album by title
   135  func (as *albums) get(title string) (album *api.Album, ok bool) {
   136  	as.mu.Lock()
   137  	defer as.mu.Unlock()
   138  	album, ok = as.byTitle[title]
   139  	return album, ok
   140  }
   141  
   142  // getDirs gets directories below an album path
   143  func (as *albums) getDirs(albumPath string) (dirs []string, ok bool) {
   144  	as.mu.Lock()
   145  	defer as.mu.Unlock()
   146  	dirs, ok = as.path[albumPath]
   147  	return dirs, ok
   148  }