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 }