// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. picasa = {} /** * LocalFile constructor. * * LocalFile object represents a file to be uploaded. */ picasa.LocalFile = function(file) { this.file_ = file; this.caption = file.name; this.dataUrl_ = null; this.mime_ = file.type; }; picasa.LocalFile.prototype = { /** * Reads data url from local file to show in img element. * @param {Function} callback Callback. */ readData_: function(callback) { if (this.dataUrl_) { callback.call(this); return; } var reader = new FileReader(); function onLoadCallback(e) { this.dataUrl_ = e.target.result; this.mime_ = this.dataUrl_.substring(0, this.dataUrl_.indexOf(';base64')); this.mime_ = this.mime_.substr(5); // skip 'data:' callback.call(this); } reader.onload = onLoadCallback.bind(this); reader.readAsDataURL(this.file_); }, showInImage: function(img) { if (this.dataUrl_) { img.setAttribute('src', this.dataUrl_); return; } this.readData_(function() { img.setAttribute('src', this.dataUrl_); }); }, /** * @return {string} Mime type of the file. */ get mimeType() { return this.mime_; } }; /** * Album constructor. * * Album object stores information about picasa album. */ picasa.Album = function(id, title, location, description, link) { this.id = id; this.title = title; this.location = location; this.description = description; this.link = link; }; /** * Client constructor. * * Client object stores user credentials and gets from and sends to picasa * web server. */ picasa.Client = function() { }; picasa.Client.prototype = { __proto__: cr.EventTarget.prototype, /** * User credentials. * @type {string} * @private */ authToken_: null, /** * User id. * @type {string} * @private */ userID_: null, /** * List of user albums. * @type {Array.<picasa.Album>} * @private */ albums_: null, /** * Url for captcha challenge, if required. * @type {string} * @private */ captchaUrl_: null, /** * Captcha toekn, if required. * @type {string} * @private */ captchaToken_: null, /** * Whether client is already authorized. * @type {boolean} */ get authorized() { return !!this.authToken_; }, /** * User id. * @type {string} */ get userID() { return this.userID_ || ''; }, /** * List of albums. * @type {Array.<picasa.Album>} */ get albums() { return this.albums_ || []; }, /** * Captcha url to show to user, if needed. * @type {string} */ get captchaUrl() { return this.captchaUrl_; }, /** * Get user credential for picasa web server. * @param {string} login User login. * @param {string} password User password. * @param {Function(string)} callback Callback, which is passed 'status' * parameter: either 'success', 'failure' or 'captcha'. * @param {?string=} opt_captcha Captcha answer, if was required. */ login: function(login, password, callback, opt_captcha) { function xhrCallback(xhr) { if (xhr.status == 200) { this.authToken_ = this.extractResponseField_(xhr.responseText, 'Auth'); this.userID_ = login; callback('success'); } else { var response = xhr.responseText; var error = this.extractResponseField_(response, 'Error'); if (error == 'CaptchaRequired') { this.captchaToken_ = this.extractResponseField_(response, 'CaptchaToken'); // Captcha url should prefixed with this. this.captchaUrl_ = 'http://www.google.com/accounts/' + this.extractResponseField_(response, 'CaptchaUrl'); callback('captcha'); return; } callback('failure'); } } var content = 'accountType=HOSTED_OR_GOOGLE&Email=' + login + '&Passwd=' + password + '&service=lh2&source=ChromeOsPWAUploader'; if (opt_captcha && this.captchaToken_) { content += '&logintoken=' + this.captchaToken_; content += '&logincaptcha=' + opt_captcha; } this.sendRequest('POST', 'https://www.google.com/accounts/ClientLogin', {'Content-type': 'application/x-www-form-urlencoded'}, content, xhrCallback.bind(this)); }, /** * Logs out. */ logout: function() { this.authToken_ = null; this.userID_ = null; this.captchaToken_ = null; this.captchatUrl_ = null; }, /** * Extracts text field from text response. * @param {string} response The response. * @param {string} field Field name to extract value of. * @return {?string} Field value or null. */ extractResponseField_: function(response, field) { var lines = response.split('\n'); field += '='; for (var line, i = 0; line = lines[i]; i++) { if (line.indexOf(field) == 0) { return line.substr(field.length); } } return null; }, /** * Sends request to web server. * @param {string} method Method to use (GET or POST). * @param {string} url Request url. * @param {Object.<string, string>} headers Request headers. * @param {*} body Request body. * @param {Function(XMLHttpRequest)} callback Callback. */ sendRequest: function(method, url, headers, body, callback) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { callback(xhr); } }; xhr.open(method, url, true); if (headers) { for (var header in headers) { if (headers.hasOwnProperty(header)) { xhr.setRequestHeader(header, headers[header]); } } } xhr.send(body); return xhr; }, /** * Gets the feed from web server and parses it. Appends user credentials. * @param {string} url Feed url. * @param {Function(*)} callback Callback. */ getFeed: function(url, callback) { var headers = {'Authorization': 'GoogleLogin auth=' + this.authToken_}; this.sendRequest('GET', url + '?alt=json', headers, null, function(xhr) { if (xhr.status == 200) { var feed = JSON.parse(xhr.responseText); callback(feed); } else { callback(null); } }); }, /** * Posts the feed to web server. Appends user credentials. * @param {string} url Feed url. * @param {Object.<string, string>} headers Request headers. * @param {*} body Post body. * @param {Function(!string)} callback Callback taking response text or * null in the case of failure. */ postFeed: function(url, headers, body, callback) { headers['Authorization'] = 'GoogleLogin auth=' + this.authToken_; return this.sendRequest('POST', url, headers, body, function(xhr) { if (xhr.status >= 200 && xhr.status <= 202) { callback(xhr.responseText); } else { callback(null); } }); }, /** * Requests albums for the user and passes them to callback. * @param {Function(Array.<picasa.Album>)} callback Callback. */ getAlbums: function(callback) { function feedCallback(feed) { feed = feed.feed; if (!feed.entry) { return; } this.albums_ = []; for (var entry, i = 0; entry = feed.entry[i]; i++) { this.albums_.push(this.albumFromEntry_(entry)); } callback(this.albums_); } this.getFeed('https://picasaweb.google.com/data/feed/api/user/' + this.userID_, feedCallback.bind(this)); }, /** * Returns album object created from entry. * @param {*} entry The feed entry corresponding to album. * @return {picasa.Album} The album object. */ albumFromEntry_: function(entry) { var altLink = ''; for (var link, j = 0; link = entry.link[j]; j++) { if (link.rel == 'alternate') { altLink = link.href; } } return new picasa.Album(entry['gphoto$id']['$t'], entry.title['$t'], entry['gphoto$location']['$t'], entry.summary['$t'], altLink); }, /** * Send request to create album. * @param {picasa.Album} album Album to create. * @param {Function(picasa.Album)} callback Callback taking updated album * (for example, with created album id). */ createAlbum: function(album, callback) { function postCallback(response) { if (response == null) { callback(null); } else { var entry = JSON.parse(response).entry; callback(this.albumFromEntry_(entry)); } } var eol = '\n'; var postData = '<entry xmlns="http://www.w3.org/2005/Atom"' + eol; postData += 'xmlns:media="http://search.yahoo.com/mrss/"' + eol; postData += 'xmlns:gphoto="http://schemas.google.com/photos/2007">' + eol; postData += '<title type="text">' + escape(album.title) + '</title>' + eol; postData += '<summary type="text">' + escape(album.description) + '</summary>' + eol; postData += '<gphoto:location>' + escape(album.location) + '</gphoto:location>' + eol; postData += '<gphoto:access>public</gphoto:access>'; postData += '<category scheme="http://schemas.google.com/g/2005#kind" ' + 'term="http://schemas.google.com/photos/2007#album"></category>' + eol; postData += '</entry>' + eol; var headers = {'Content-Type': 'application/atom+xml'}; this.postFeed('https://picasaweb.google.com/data/feed/api/user/' + this.userID_ + '?alt=json', headers, postData, postCallback.bind(this)); }, /** * Uploads file to the given album. * @param {picasa.Album} album Album to upload to. * @param {picasa.LocalFile} file File to upload. * @param {Function(?string)} callback Callback. */ uploadFile: function(album, file, callback) { var postData = file.file_; var headers = { 'Content-Type': file.mimeType, 'Slug': file.file_.name}; return this.postFeed('https://picasaweb.google.com/data/feed/api/user/' + this.userID_ + '/albumid/' + album.id, headers, postData, callback); } };