1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 picasa = {} 6 7 /** 8 * LocalFile constructor. 9 * 10 * LocalFile object represents a file to be uploaded. 11 */ 12 picasa.LocalFile = function(file) { 13 this.file_ = file; 14 this.caption = file.name; 15 16 this.dataUrl_ = null; 17 this.mime_ = file.type; 18 }; 19 20 picasa.LocalFile.prototype = { 21 /** 22 * Reads data url from local file to show in img element. 23 * @param {Function} callback Callback. 24 */ 25 readData_: function(callback) { 26 if (this.dataUrl_) { 27 callback.call(this); 28 return; 29 } 30 31 var reader = new FileReader(); 32 function onLoadCallback(e) { 33 this.dataUrl_ = e.target.result; 34 this.mime_ = this.dataUrl_.substring(0, this.dataUrl_.indexOf(';base64')); 35 this.mime_ = this.mime_.substr(5); // skip 'data:' 36 callback.call(this); 37 } 38 reader.onload = onLoadCallback.bind(this); 39 reader.readAsDataURL(this.file_); 40 }, 41 42 showInImage: function(img) { 43 if (this.dataUrl_) { 44 img.setAttribute('src', this.dataUrl_); 45 return; 46 } 47 48 this.readData_(function() { 49 img.setAttribute('src', this.dataUrl_); 50 }); 51 }, 52 53 /** 54 * @return {string} Mime type of the file. 55 */ 56 get mimeType() { 57 return this.mime_; 58 } 59 }; 60 61 62 /** 63 * Album constructor. 64 * 65 * Album object stores information about picasa album. 66 */ 67 picasa.Album = function(id, title, location, description, link) { 68 this.id = id; 69 this.title = title; 70 this.location = location; 71 this.description = description; 72 this.link = link; 73 }; 74 75 76 /** 77 * Client constructor. 78 * 79 * Client object stores user credentials and gets from and sends to picasa 80 * web server. 81 */ 82 picasa.Client = function() { 83 }; 84 85 86 picasa.Client.prototype = { 87 __proto__: cr.EventTarget.prototype, 88 89 /** 90 * User credentials. 91 * @type {string} 92 * @private 93 */ 94 authToken_: null, 95 96 /** 97 * User id. 98 * @type {string} 99 * @private 100 */ 101 userID_: null, 102 103 /** 104 * List of user albums. 105 * @type {Array.<picasa.Album>} 106 * @private 107 */ 108 albums_: null, 109 110 /** 111 * Url for captcha challenge, if required. 112 * @type {string} 113 * @private 114 */ 115 captchaUrl_: null, 116 117 /** 118 * Captcha toekn, if required. 119 * @type {string} 120 * @private 121 */ 122 captchaToken_: null, 123 124 /** 125 * Whether client is already authorized. 126 * @type {boolean} 127 */ 128 get authorized() { 129 return !!this.authToken_; 130 }, 131 132 /** 133 * User id. 134 * @type {string} 135 */ 136 get userID() { 137 return this.userID_ || ''; 138 }, 139 140 /** 141 * List of albums. 142 * @type {Array.<picasa.Album>} 143 */ 144 get albums() { 145 return this.albums_ || []; 146 }, 147 148 /** 149 * Captcha url to show to user, if needed. 150 * @type {string} 151 */ 152 get captchaUrl() { 153 return this.captchaUrl_; 154 }, 155 156 /** 157 * Get user credential for picasa web server. 158 * @param {string} login User login. 159 * @param {string} password User password. 160 * @param {Function(string)} callback Callback, which is passed 'status' 161 * parameter: either 'success', 'failure' or 'captcha'. 162 * @param {?string=} opt_captcha Captcha answer, if was required. 163 */ 164 login: function(login, password, callback, opt_captcha) { 165 function xhrCallback(xhr) { 166 if (xhr.status == 200) { 167 this.authToken_ = this.extractResponseField_(xhr.responseText, 'Auth'); 168 this.userID_ = login; 169 callback('success'); 170 } else { 171 var response = xhr.responseText; 172 var error = this.extractResponseField_(response, 'Error'); 173 if (error == 'CaptchaRequired') { 174 this.captchaToken_ = this.extractResponseField_(response, 175 'CaptchaToken'); 176 // Captcha url should prefixed with this. 177 this.captchaUrl_ = 'http://www.google.com/accounts/' + 178 this.extractResponseField_(response, 'CaptchaUrl'); 179 callback('captcha'); 180 return; 181 } 182 callback('failure'); 183 } 184 } 185 186 var content = 'accountType=HOSTED_OR_GOOGLE&Email=' + login + 187 '&Passwd=' + password + '&service=lh2&source=ChromeOsPWAUploader'; 188 if (opt_captcha && this.captchaToken_) { 189 content += '&logintoken=' + this.captchaToken_; 190 content += '&logincaptcha=' + opt_captcha; 191 } 192 this.sendRequest('POST', 'https://www.google.com/accounts/ClientLogin', 193 {'Content-type': 'application/x-www-form-urlencoded'}, 194 content, 195 xhrCallback.bind(this)); 196 }, 197 198 /** 199 * Logs out. 200 */ 201 logout: function() { 202 this.authToken_ = null; 203 this.userID_ = null; 204 this.captchaToken_ = null; 205 this.captchatUrl_ = null; 206 }, 207 208 /** 209 * Extracts text field from text response. 210 * @param {string} response The response. 211 * @param {string} field Field name to extract value of. 212 * @return {?string} Field value or null. 213 */ 214 extractResponseField_: function(response, field) { 215 var lines = response.split('\n'); 216 field += '='; 217 for (var line, i = 0; line = lines[i]; i++) { 218 if (line.indexOf(field) == 0) { 219 return line.substr(field.length); 220 } 221 } 222 return null; 223 }, 224 225 /** 226 * Sends request to web server. 227 * @param {string} method Method to use (GET or POST). 228 * @param {string} url Request url. 229 * @param {Object.<string, string>} headers Request headers. 230 * @param {*} body Request body. 231 * @param {Function(XMLHttpRequest)} callback Callback. 232 */ 233 sendRequest: function(method, url, headers, body, callback) { 234 var xhr = new XMLHttpRequest(); 235 xhr.onreadystatechange = function() { 236 if (xhr.readyState == 4) { 237 callback(xhr); 238 } 239 }; 240 xhr.open(method, url, true); 241 if (headers) { 242 for (var header in headers) { 243 if (headers.hasOwnProperty(header)) { 244 xhr.setRequestHeader(header, headers[header]); 245 } 246 } 247 } 248 xhr.send(body); 249 return xhr; 250 }, 251 252 /** 253 * Gets the feed from web server and parses it. Appends user credentials. 254 * @param {string} url Feed url. 255 * @param {Function(*)} callback Callback. 256 */ 257 getFeed: function(url, callback) { 258 var headers = {'Authorization': 'GoogleLogin auth=' + this.authToken_}; 259 this.sendRequest('GET', url + '?alt=json', headers, null, function(xhr) { 260 if (xhr.status == 200) { 261 var feed = JSON.parse(xhr.responseText); 262 callback(feed); 263 } else { 264 callback(null); 265 } 266 }); 267 }, 268 269 /** 270 * Posts the feed to web server. Appends user credentials. 271 * @param {string} url Feed url. 272 * @param {Object.<string, string>} headers Request headers. 273 * @param {*} body Post body. 274 * @param {Function(!string)} callback Callback taking response text or 275 * null in the case of failure. 276 */ 277 postFeed: function(url, headers, body, callback) { 278 headers['Authorization'] = 'GoogleLogin auth=' + this.authToken_; 279 return this.sendRequest('POST', url, headers, body, function(xhr) { 280 if (xhr.status >= 200 && xhr.status <= 202) { 281 callback(xhr.responseText); 282 } else { 283 callback(null); 284 } 285 }); 286 }, 287 288 /** 289 * Requests albums for the user and passes them to callback. 290 * @param {Function(Array.<picasa.Album>)} callback Callback. 291 */ 292 getAlbums: function(callback) { 293 function feedCallback(feed) { 294 feed = feed.feed; 295 if (!feed.entry) { 296 return; 297 } 298 this.albums_ = []; 299 for (var entry, i = 0; entry = feed.entry[i]; i++) { 300 this.albums_.push(this.albumFromEntry_(entry)); 301 } 302 callback(this.albums_); 303 } 304 305 this.getFeed('https://picasaweb.google.com/data/feed/api/user/' + 306 this.userID_, feedCallback.bind(this)); 307 }, 308 309 /** 310 * Returns album object created from entry. 311 * @param {*} entry The feed entry corresponding to album. 312 * @return {picasa.Album} The album object. 313 */ 314 albumFromEntry_: function(entry) { 315 var altLink = ''; 316 for (var link, j = 0; link = entry.link[j]; j++) { 317 if (link.rel == 'alternate') { 318 altLink = link.href; 319 } 320 } 321 return new picasa.Album(entry['gphoto$id']['$t'], entry.title['$t'], 322 entry['gphoto$location']['$t'], entry.summary['$t'], altLink); 323 }, 324 325 /** 326 * Send request to create album. 327 * @param {picasa.Album} album Album to create. 328 * @param {Function(picasa.Album)} callback Callback taking updated album 329 * (for example, with created album id). 330 */ 331 createAlbum: function(album, callback) { 332 function postCallback(response) { 333 if (response == null) { 334 callback(null); 335 } else { 336 var entry = JSON.parse(response).entry; 337 callback(this.albumFromEntry_(entry)); 338 } 339 } 340 341 var eol = '\n'; 342 var postData = '<entry xmlns="http://www.w3.org/2005/Atom"' + eol; 343 postData += 'xmlns:media="http://search.yahoo.com/mrss/"' + eol; 344 postData += 'xmlns:gphoto="http://schemas.google.com/photos/2007">' + eol; 345 postData += '<title type="text">' + escape(album.title) + '</title>' + eol; 346 postData += '<summary type="text">' + escape(album.description) + 347 '</summary>' + eol; 348 postData += '<gphoto:location>' + escape(album.location) + 349 '</gphoto:location>' + eol; 350 postData += '<gphoto:access>public</gphoto:access>'; 351 postData += '<category scheme="http://schemas.google.com/g/2005#kind" ' + 352 'term="http://schemas.google.com/photos/2007#album"></category>' + eol; 353 postData += '</entry>' + eol; 354 355 var headers = {'Content-Type': 'application/atom+xml'}; 356 this.postFeed('https://picasaweb.google.com/data/feed/api/user/' + 357 this.userID_ + '?alt=json', headers, postData, postCallback.bind(this)); 358 }, 359 360 /** 361 * Uploads file to the given album. 362 * @param {picasa.Album} album Album to upload to. 363 * @param {picasa.LocalFile} file File to upload. 364 * @param {Function(?string)} callback Callback. 365 */ 366 uploadFile: function(album, file, callback) { 367 var postData = file.file_; 368 var headers = { 369 'Content-Type': file.mimeType, 370 'Slug': file.file_.name}; 371 return this.postFeed('https://picasaweb.google.com/data/feed/api/user/' + 372 this.userID_ + '/albumid/' + album.id, headers, postData, callback); 373 } 374 }; 375