-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathapi.go
182 lines (156 loc) · 5.09 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package smugmug
import (
"errors"
"fmt"
"os"
"time"
log "github.com/sirupsen/logrus"
)
type requestsHandler interface {
get(string, interface{}) error
}
// userAlbums returns the list of albums belonging to the suer
func (w *Worker) userAlbums() ([]album, error) {
uri := w.userAlbumsURI()
return w.albums(uri)
}
// currentUser returns the nickname of the authenticated user
func (w *Worker) currentUser() (string, error) {
var u currentUser
if err := w.req.get("/api/v2!authuser", &u); err != nil {
return "", err
}
return u.Response.User.NickName, nil
}
// userAlbumsURI returns the URI of the first page of the user albums. It's intended to be used
// as argument for a call to albums()
func (w *Worker) userAlbumsURI() string {
var u user
path := fmt.Sprintf("/api/v2/user/%s", w.cfg.username)
w.req.get(path, &u)
return u.Response.User.Uris.UserAlbums.URI
}
// albums make multiple calls to obtain the full list of user albums. It calls the albums endpoint
// unless the "NextPage" value in the response is empty
func (w *Worker) albums(firstURI string) ([]album, error) {
uri := firstURI
var albums []album
for uri != "" {
var a albumsResponse
if err := w.req.get(uri, &a); err != nil {
return albums, fmt.Errorf("error getting albums from %s. Error: %v", uri, err)
}
albums = append(albums, a.Response.Album...)
uri = a.Response.Pages.NextPage
}
return albums, nil
}
// albumImages make multiple calls to obtain all images of an album. It calls the album images
// endpoint unless the "NextPage" value in the response is empty
func (w *Worker) albumImages(firstURI string, albumPath string) ([]albumImage, error) {
uri := firstURI
var images []albumImage
for uri != "" {
if w.quitting {
return nil, nil
}
var a albumImagesResponse
if err := w.req.get(uri, &a); err != nil {
return images, fmt.Errorf("error getting album images from %s. Error: %v", uri, err)
}
// If the album is empty, a.Response.AlbumImage is missing instead of an empty array (weird...)
if a.Response.AlbumImage == nil {
log.Infof("album is empty: %s", albumPath)
break
}
// Loop over response in inject the albumPath and then append to the images
for _, i := range a.Response.AlbumImage {
i.AlbumPath = albumPath
if err := i.buildFilename(w.filenameTmpl); err != nil {
return nil, fmt.Errorf("cannot build image filename: %v", err)
}
images = append(images, i)
}
uri = a.Response.Pages.NextPage
}
return images, nil
}
func (w *Worker) imageTimestamp(img albumImage) time.Time {
var i imageMetadataResponse
if err := w.req.get(img.Uris.ImageMetadata.Uri, &i); err != nil {
return time.Time{}
}
return i.Response.DateTimeCreated
}
// saveImages calls saveImage or saveVideo to save a list of album images to the given folder
func (w *Worker) saveImages(images []albumImage, folder string) {
for _, image := range images {
if w.quitting {
return
}
w.downloadsCh <- &downloadInfo{
image: image,
folder: folder,
}
}
}
// saveImage saves an image to the given folder unless its name is empty
func (w *Worker) saveImage(image albumImage, folder string) error {
if image.Name() == "" {
return errors.New("unable to find valid image filename, skipping")
}
dest := fmt.Sprintf("%s/%s", folder, image.Name())
log.Debug(image.ArchivedUri)
ok, err := w.downloadFn(dest, image.ArchivedUri, image.ArchivedSize)
if err != nil {
return err
}
if w.cfg.UseMetadataTimes && (ok || w.cfg.ForceMetadataTimes) {
return w.setChTime(image, dest)
}
return nil
}
// saveVideo saves a video to the given folder unless its name is empty or is still under processing
func (w *Worker) saveVideo(image albumImage, folder string) error {
if image.Name() == "" {
return errors.New("unable to find valid video filename, skipping")
}
dest := fmt.Sprintf("%s/%s", folder, image.Name())
if image.Processing {
if image.Status == "Preprocess" && image.SubStatus == "CanNotProcess" {
return fmt.Errorf("skipping video %s because cannot be processed, %#v", image.Name(), image)
}
if !w.cfg.ForceVideoDownload { // Skip videos if under processing
return fmt.Errorf("skipping video %s because under processing, %#v", image.Name(), image)
}
}
var v albumVideo
log.Debug("(saveVideo) getting ", image.Uris.LargestVideo.Uri)
if err := w.req.get(image.Uris.LargestVideo.Uri, &v); err != nil {
return fmt.Errorf("cannot get URI for video %+v. Error: %v", image, err)
}
ok, err := w.downloadFn(dest, v.Response.LargestVideo.Url, v.Response.LargestVideo.Size)
if err != nil {
return err
}
if w.cfg.UseMetadataTimes && (ok || w.cfg.ForceMetadataTimes) {
return w.setChTime(image, dest)
}
return nil
}
func (w *Worker) setChTime(image albumImage, dest string) error {
// Try first with the date in the image, to avoid making an additional call
dt := image.DateTimeOriginal
if dt == "" {
dt = image.DateTimeUploaded
}
created, err := time.Parse(time.RFC3339, dt)
if err != nil || created.IsZero() {
created = w.imageTimestamp(image)
}
if !created.IsZero() {
log.Debugf("setting chtime %v for %s", created, dest)
return os.Chtimes(dest, time.Now(), created)
}
return nil
}