2016-06-03 15:14:12 +06:00
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers , whose names
* are too numerous to list here . Please refer to the COPYRIGHT
* file distributed with this source distribution .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version 2
* of the License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*
*/
# define FORBIDDEN_SYMBOL_ALLOW_ALL
# include "backends/cloud/googledrive/googledrivestorage.h"
# include "backends/cloud/cloudmanager.h"
# include "backends/cloud/googledrive/googledrivetokenrefresher.h"
# include "backends/networking/curl/connectionmanager.h"
# include "backends/networking/curl/curljsonrequest.h"
# include "backends/networking/curl/networkreadstream.h"
# include "common/config-manager.h"
# include "common/debug.h"
# include "common/json.h"
# include <curl/curl.h>
2016-06-06 20:04:13 +06:00
# include "googledrivelistdirectorybyidrequest.h"
# include "googledriveresolveidrequest.h"
2016-06-06 21:24:53 +06:00
# include "googledrivecreatedirectoryrequest.h"
2016-06-07 13:07:46 +06:00
# include "googledrivelistdirectoryrequest.h"
2016-06-08 14:17:35 +06:00
# include "googledrivestreamfilerequest.h"
# include "googledrivedownloadrequest.h"
2016-06-08 16:46:18 +06:00
# include "googledriveuploadrequest.h"
2016-06-03 15:14:12 +06:00
namespace Cloud {
namespace GoogleDrive {
char * GoogleDriveStorage : : KEY ; //can't use ConfMan there yet, loading it on instance creation/auth
char * GoogleDriveStorage : : SECRET ; //TODO: hide these secrets somehow
void GoogleDriveStorage : : loadKeyAndSecret ( ) {
Common : : String k = ConfMan . get ( " GOOGLE_DRIVE_KEY " , " cloud " ) ;
KEY = new char [ k . size ( ) + 1 ] ;
memcpy ( KEY , k . c_str ( ) , k . size ( ) ) ;
KEY [ k . size ( ) ] = 0 ;
k = ConfMan . get ( " GOOGLE_DRIVE_SECRET " , " cloud " ) ;
SECRET = new char [ k . size ( ) + 1 ] ;
memcpy ( SECRET , k . c_str ( ) , k . size ( ) ) ;
SECRET [ k . size ( ) ] = 0 ;
}
GoogleDriveStorage : : GoogleDriveStorage ( Common : : String accessToken , Common : : String refreshToken ) :
_token ( accessToken ) , _refreshToken ( refreshToken ) { }
GoogleDriveStorage : : GoogleDriveStorage ( Common : : String code ) {
getAccessToken ( new Common : : Callback < GoogleDriveStorage , BoolResponse > ( this , & GoogleDriveStorage : : codeFlowComplete ) , code ) ;
}
GoogleDriveStorage : : ~ GoogleDriveStorage ( ) { }
void GoogleDriveStorage : : getAccessToken ( BoolCallback callback , Common : : String code ) {
bool codeFlow = ( code ! = " " ) ;
if ( ! codeFlow & & _refreshToken = = " " ) {
warning ( " GoogleDriveStorage: no refresh token available to get new access token. " ) ;
if ( callback ) ( * callback ) ( BoolResponse ( nullptr , false ) ) ;
return ;
}
Networking : : JsonCallback innerCallback = new Common : : CallbackBridge < GoogleDriveStorage , BoolResponse , Networking : : JsonResponse > ( this , & GoogleDriveStorage : : tokenRefreshed , callback ) ;
Networking : : CurlJsonRequest * request = new Networking : : CurlJsonRequest ( innerCallback , getErrorPrintingCallback ( ) , " https://accounts.google.com/o/oauth2/token " ) ; //TODO
if ( codeFlow ) {
request - > addPostField ( " code= " + code ) ;
request - > addPostField ( " grant_type=authorization_code " ) ;
} else {
request - > addPostField ( " refresh_token= " + _refreshToken ) ;
request - > addPostField ( " grant_type=refresh_token " ) ;
}
request - > addPostField ( " client_id= " + Common : : String ( KEY ) ) ;
request - > addPostField ( " client_secret= " + Common : : String ( SECRET ) ) ;
request - > addPostField ( " &redirect_uri=http%3A%2F%2Flocalhost " ) ;
addRequest ( request ) ;
}
void GoogleDriveStorage : : tokenRefreshed ( BoolCallback callback , Networking : : JsonResponse response ) {
Common : : JSONValue * json = response . value ;
if ( ! json ) {
warning ( " GoogleDriveStorage: got NULL instead of JSON " ) ;
if ( callback ) ( * callback ) ( BoolResponse ( nullptr , false ) ) ;
return ;
}
Common : : JSONObject result = json - > asObject ( ) ;
if ( ! result . contains ( " access_token " ) ) {
warning ( " Bad response, no token passed " ) ;
debug ( " %s " , json - > stringify ( ) . c_str ( ) ) ;
if ( callback ) ( * callback ) ( BoolResponse ( nullptr , false ) ) ;
} else {
_token = result . getVal ( " access_token " ) - > asString ( ) ;
if ( ! result . contains ( " refresh_token " ) )
warning ( " No refresh_token passed " ) ;
else
_refreshToken = result . getVal ( " refresh_token " ) - > asString ( ) ;
CloudMan . save ( ) ; //ask CloudManager to save our new refreshToken
if ( callback ) ( * callback ) ( BoolResponse ( nullptr , true ) ) ;
}
delete json ;
}
void GoogleDriveStorage : : codeFlowComplete ( BoolResponse response ) {
if ( ! response . value ) {
warning ( " GoogleDriveStorage: failed to get access token through code flow " ) ;
return ;
}
ConfMan . removeKey ( " googledrive_code " , " cloud " ) ;
CloudMan . addStorage ( this ) ;
ConfMan . flushToDisk ( ) ;
debug ( " Done! You can use Google Drive now! Look: " ) ;
CloudMan . testFeature ( ) ;
}
void GoogleDriveStorage : : saveConfig ( Common : : String keyPrefix ) {
ConfMan . set ( keyPrefix + " type " , " Google Drive " , " cloud " ) ;
ConfMan . set ( keyPrefix + " access_token " , _token , " cloud " ) ;
ConfMan . set ( keyPrefix + " refresh_token " , _refreshToken , " cloud " ) ;
}
2016-06-08 18:51:00 +06:00
Common : : String GoogleDriveStorage : : name ( ) const {
return " Google Drive " ;
}
2016-06-03 15:14:12 +06:00
namespace {
uint64 atoull ( Common : : String s ) {
uint64 result = 0 ;
for ( uint32 i = 0 ; i < s . size ( ) ; + + i ) {
if ( s [ i ] < ' 0 ' | | s [ i ] > ' 9 ' ) break ;
result = result * 10L + ( s [ i ] - ' 0 ' ) ;
}
return result ;
}
}
void GoogleDriveStorage : : infoInnerCallback ( StorageInfoCallback outerCallback , Networking : : JsonResponse response ) {
Common : : JSONValue * json = response . value ;
if ( ! json ) {
warning ( " NULL passed instead of JSON " ) ;
delete outerCallback ;
return ;
}
if ( outerCallback ) {
Common : : JSONObject info = json - > asObject ( ) ;
Common : : String uid , name , email ;
uint64 quotaUsed = 0 , quotaAllocated = 0 ;
if ( info . contains ( " user " ) & & info . getVal ( " user " ) - > isObject ( ) ) {
//"me":true, "kind":"drive#user","photoLink": "",
//"displayName":"Alexander Tkachev","emailAddress":"alexander@tkachov.ru","permissionId":""
Common : : JSONObject user = info . getVal ( " user " ) - > asObject ( ) ;
uid = user . getVal ( " permissionId " ) - > asString ( ) ; //not sure it's user's id, but who cares anyway?
name = user . getVal ( " displayName " ) - > asString ( ) ;
email = user . getVal ( " emailAddress " ) - > asString ( ) ;
}
if ( info . contains ( " storageQuota " ) & & info . getVal ( " storageQuota " ) - > isObject ( ) ) {
//"usageInDrive":"6332462","limit":"18253611008","usage":"6332462","usageInDriveTrash":"0"
Common : : JSONObject storageQuota = info . getVal ( " storageQuota " ) - > asObject ( ) ;
Common : : String usage = storageQuota . getVal ( " usage " ) - > asString ( ) ;
Common : : String limit = storageQuota . getVal ( " limit " ) - > asString ( ) ;
quotaUsed = atoull ( usage ) ;
quotaAllocated = atoull ( limit ) ;
}
( * outerCallback ) ( StorageInfoResponse ( nullptr , StorageInfo ( uid , name , email , quotaUsed , quotaAllocated ) ) ) ;
delete outerCallback ;
}
delete json ;
}
2016-06-06 20:04:13 +06:00
void GoogleDriveStorage : : createDirectoryInnerCallback ( BoolCallback outerCallback , Networking : : JsonResponse response ) {
Common : : JSONValue * json = response . value ;
if ( ! json ) {
warning ( " NULL passed instead of JSON " ) ;
delete outerCallback ;
return ;
}
if ( outerCallback ) {
2016-06-06 21:24:53 +06:00
Common : : JSONObject info = json - > asObject ( ) ;
( * outerCallback ) ( BoolResponse ( nullptr , info . contains ( " id " ) ) ) ;
2016-06-06 20:04:13 +06:00
delete outerCallback ;
}
delete json ;
}
2016-06-03 15:14:12 +06:00
void GoogleDriveStorage : : printJson ( Networking : : JsonResponse response ) {
Common : : JSONValue * json = response . value ;
if ( ! json ) {
warning ( " printJson: NULL " ) ;
return ;
}
debug ( " %s " , json - > stringify ( ) . c_str ( ) ) ;
delete json ;
}
2016-06-06 20:04:13 +06:00
Networking : : Request * GoogleDriveStorage : : resolveFileId ( Common : : String path , UploadCallback callback , Networking : : ErrorCallback errorCallback ) {
if ( ! errorCallback ) errorCallback = getErrorPrintingCallback ( ) ;
if ( ! callback ) callback = new Common : : Callback < GoogleDriveStorage , UploadResponse > ( this , & GoogleDriveStorage : : printFile ) ;
return addRequest ( new GoogleDriveResolveIdRequest ( this , path , callback , errorCallback ) ) ;
}
2016-06-03 15:14:12 +06:00
Networking : : Request * GoogleDriveStorage : : listDirectory ( Common : : String path , ListDirectoryCallback callback , Networking : : ErrorCallback errorCallback , bool recursive ) {
2016-06-07 13:07:46 +06:00
if ( ! errorCallback ) errorCallback = getErrorPrintingCallback ( ) ;
if ( ! callback ) callback = new Common : : Callback < GoogleDriveStorage , FileArrayResponse > ( this , & GoogleDriveStorage : : printFiles ) ;
return addRequest ( new GoogleDriveListDirectoryRequest ( this , path , callback , errorCallback , recursive ) ) ;
2016-06-03 15:14:12 +06:00
}
2016-06-06 20:04:13 +06:00
Networking : : Request * GoogleDriveStorage : : listDirectoryById ( Common : : String id , ListDirectoryCallback callback , Networking : : ErrorCallback errorCallback ) {
if ( ! errorCallback ) errorCallback = getErrorPrintingCallback ( ) ;
if ( ! callback ) callback = new Common : : Callback < GoogleDriveStorage , FileArrayResponse > ( this , & GoogleDriveStorage : : printFiles ) ;
return addRequest ( new GoogleDriveListDirectoryByIdRequest ( this , id , callback , errorCallback ) ) ;
}
2016-06-03 15:14:12 +06:00
Networking : : Request * GoogleDriveStorage : : upload ( Common : : String path , Common : : SeekableReadStream * contents , UploadCallback callback , Networking : : ErrorCallback errorCallback ) {
2016-06-08 16:46:18 +06:00
return addRequest ( new GoogleDriveUploadRequest ( this , path , contents , callback , errorCallback ) ) ;
2016-06-03 15:14:12 +06:00
}
2016-06-08 14:17:35 +06:00
Networking : : Request * GoogleDriveStorage : : streamFile ( Common : : String path , Networking : : NetworkReadStreamCallback outerCallback , Networking : : ErrorCallback errorCallback ) {
return addRequest ( new GoogleDriveStreamFileRequest ( this , path , outerCallback , errorCallback ) ) ;
2016-06-08 13:02:49 +06:00
}
Networking : : Request * GoogleDriveStorage : : streamFileById ( Common : : String id , Networking : : NetworkReadStreamCallback callback , Networking : : ErrorCallback errorCallback ) {
2016-06-08 14:17:35 +06:00
if ( callback ) {
Common : : String url = " https://www.googleapis.com/drive/v3/files/ " + id + " ?alt=media " ;
Common : : String header = " Authorization: Bearer " + _token ;
curl_slist * headersList = curl_slist_append ( nullptr , header . c_str ( ) ) ;
Networking : : NetworkReadStream * stream = new Networking : : NetworkReadStream ( url . c_str ( ) , headersList , " " ) ;
( * callback ) ( Networking : : NetworkReadStreamResponse ( nullptr , stream ) ) ;
}
delete callback ;
delete errorCallback ;
return nullptr ;
2016-06-08 13:02:49 +06:00
}
Networking : : Request * GoogleDriveStorage : : download ( Common : : String remotePath , Common : : String localPath , BoolCallback callback , Networking : : ErrorCallback errorCallback ) {
2016-06-08 14:17:35 +06:00
return addRequest ( new GoogleDriveDownloadRequest ( this , remotePath , localPath , callback , errorCallback ) ) ;
2016-06-03 15:14:12 +06:00
}
void GoogleDriveStorage : : fileDownloaded ( BoolResponse response ) {
if ( response . value ) debug ( " file downloaded! " ) ;
else debug ( " download failed! " ) ;
}
void GoogleDriveStorage : : printFiles ( FileArrayResponse response ) {
debug ( " files: " ) ;
Common : : Array < StorageFile > & files = response . value ;
2016-06-06 20:04:13 +06:00
for ( uint32 i = 0 ; i < files . size ( ) ; + + i ) {
debug ( " \t %s%s " , files [ i ] . name ( ) . c_str ( ) , files [ i ] . isDirectory ( ) ? " (directory) " : " " ) ;
2016-06-03 15:14:12 +06:00
debug ( " \t %s " , files [ i ] . path ( ) . c_str ( ) ) ;
2016-06-08 12:34:13 +06:00
debug ( " \t %s " , files [ i ] . id ( ) . c_str ( ) ) ;
2016-06-06 20:04:13 +06:00
debug ( " " ) ;
}
2016-06-03 15:14:12 +06:00
}
void GoogleDriveStorage : : printBool ( BoolResponse response ) {
debug ( " bool: %s " , response . value ? " true " : " false " ) ;
}
void GoogleDriveStorage : : printFile ( UploadResponse response ) {
debug ( " \n uploaded file info: " ) ;
2016-06-06 20:04:13 +06:00
debug ( " \t id: %s " , response . value . path ( ) . c_str ( ) ) ;
debug ( " \t name: %s " , response . value . name ( ) . c_str ( ) ) ;
2016-06-03 15:14:12 +06:00
debug ( " \t size: %u " , response . value . size ( ) ) ;
debug ( " \t timestamp: %u " , response . value . timestamp ( ) ) ;
}
void GoogleDriveStorage : : printInfo ( StorageInfoResponse response ) {
debug ( " \n user info: " ) ;
debug ( " \t name: %s " , response . value . name ( ) . c_str ( ) ) ;
debug ( " \t email: %s " , response . value . email ( ) . c_str ( ) ) ;
debug ( " \t disk usage: %llu/%llu " , response . value . used ( ) , response . value . available ( ) ) ;
}
Networking : : Request * GoogleDriveStorage : : createDirectory ( Common : : String path , BoolCallback callback , Networking : : ErrorCallback errorCallback ) {
if ( ! errorCallback ) errorCallback = getErrorPrintingCallback ( ) ;
2016-06-06 21:24:53 +06:00
if ( ! callback ) callback = new Common : : Callback < GoogleDriveStorage , BoolResponse > ( this , & GoogleDriveStorage : : printBool ) ;
//find out the parent path and directory name
Common : : String parentPath = " " , directoryName = path ;
for ( uint32 i = path . size ( ) ; i > 0 ; - - i ) {
if ( path [ i - 1 ] = = ' / ' | | path [ i - 1 ] = = ' \\ ' ) {
parentPath = path ;
parentPath . erase ( i - 1 ) ;
directoryName . erase ( 0 , i ) ;
break ;
}
}
return addRequest ( new GoogleDriveCreateDirectoryRequest ( this , parentPath , directoryName , callback , errorCallback ) ) ;
2016-06-03 15:14:12 +06:00
}
2016-06-06 20:04:13 +06:00
Networking : : Request * GoogleDriveStorage : : createDirectoryWithParentId ( Common : : String parentId , Common : : String name , BoolCallback callback , Networking : : ErrorCallback errorCallback ) {
if ( ! errorCallback ) errorCallback = getErrorPrintingCallback ( ) ;
2016-06-06 21:24:53 +06:00
Common : : String url = " https://www.googleapis.com/drive/v3/files " ;
2016-06-06 20:04:13 +06:00
Networking : : JsonCallback innerCallback = new Common : : CallbackBridge < GoogleDriveStorage , BoolResponse , Networking : : JsonResponse > ( this , & GoogleDriveStorage : : createDirectoryInnerCallback , callback ) ;
Networking : : CurlJsonRequest * request = new GoogleDriveTokenRefresher ( this , innerCallback , errorCallback , url . c_str ( ) ) ;
request - > addHeader ( " Authorization: Bearer " + accessToken ( ) ) ;
request - > addHeader ( " Content-Type: application/json " ) ;
Common : : JSONArray parentsArray ;
parentsArray . push_back ( new Common : : JSONValue ( parentId ) ) ;
Common : : JSONObject jsonRequestParameters ;
jsonRequestParameters . setVal ( " mimeType " , new Common : : JSONValue ( " application/vnd.google-apps.folder " ) ) ;
jsonRequestParameters . setVal ( " name " , new Common : : JSONValue ( name ) ) ;
jsonRequestParameters . setVal ( " parents " , new Common : : JSONValue ( parentsArray ) ) ;
Common : : JSONValue value ( jsonRequestParameters ) ;
request - > addPostField ( Common : : JSON : : stringify ( & value ) ) ;
return addRequest ( request ) ;
}
2016-06-03 15:14:12 +06:00
Networking : : Request * GoogleDriveStorage : : info ( StorageInfoCallback callback , Networking : : ErrorCallback errorCallback ) {
if ( ! callback ) callback = new Common : : Callback < GoogleDriveStorage , StorageInfoResponse > ( this , & GoogleDriveStorage : : printInfo ) ;
Networking : : JsonCallback innerCallback = new Common : : CallbackBridge < GoogleDriveStorage , StorageInfoResponse , Networking : : JsonResponse > ( this , & GoogleDriveStorage : : infoInnerCallback , callback ) ;
Networking : : CurlJsonRequest * request = new GoogleDriveTokenRefresher ( this , innerCallback , errorCallback , " https://www.googleapis.com/drive/v3/about?fields=storageQuota,user " ) ;
request - > addHeader ( " Authorization: Bearer " + _token ) ;
return addRequest ( request ) ;
}
2016-06-07 16:27:04 +06:00
Common : : String GoogleDriveStorage : : savesDirectoryPath ( ) { return " scummvm/saves/ " ; }
2016-06-03 15:14:12 +06:00
GoogleDriveStorage * GoogleDriveStorage : : loadFromConfig ( Common : : String keyPrefix ) {
loadKeyAndSecret ( ) ;
if ( ! ConfMan . hasKey ( keyPrefix + " access_token " , " cloud " ) ) {
warning ( " No access_token found " ) ;
return 0 ;
}
if ( ! ConfMan . hasKey ( keyPrefix + " refresh_token " , " cloud " ) ) {
warning ( " No refresh_token found " ) ;
return 0 ;
}
Common : : String accessToken = ConfMan . get ( keyPrefix + " access_token " , " cloud " ) ;
Common : : String refreshToken = ConfMan . get ( keyPrefix + " refresh_token " , " cloud " ) ;
return new GoogleDriveStorage ( accessToken , refreshToken ) ;
}
Common : : String GoogleDriveStorage : : getAuthLink ( ) {
Common : : String url = " https://accounts.google.com/o/oauth2/auth " ;
url + = " ?response_type=code " ;
url + = " &redirect_uri=http://localhost " ; //that's for copy-pasting
//url += "&redirect_uri=http%3A%2F%2Flocalhost"; //that's "http://localhost" for automatic opening
url + = " &client_id= " ; url + = KEY ;
2016-06-07 13:49:26 +06:00
url + = " &scope=https://www.googleapis.com/auth/drive " ; //for copy-pasting
2016-06-03 15:14:12 +06:00
return url ;
}
void GoogleDriveStorage : : authThroughConsole ( ) {
if ( ! ConfMan . hasKey ( " GOOGLE_DRIVE_KEY " , " cloud " ) | | ! ConfMan . hasKey ( " GOOGLE_DRIVE_SECRET " , " cloud " ) ) {
warning ( " No Google Drive keys available, cannot do auth " ) ;
return ;
}
loadKeyAndSecret ( ) ;
if ( ConfMan . hasKey ( " googledrive_code " , " cloud " ) ) {
//phase 2: get access_token using specified code
new GoogleDriveStorage ( ConfMan . get ( " googledrive_code " , " cloud " ) ) ;
return ;
}
debug ( " Navigate to this URL and press \" Allow \" : " ) ;
debug ( " %s \n " , getAuthLink ( ) . c_str ( ) ) ;
debug ( " Then, add googledrive_code key in [cloud] section of configuration file. You should copy the <code> value from URL and put it as value for that key. \n " ) ;
debug ( " Navigate to this URL to get more information on ScummVM's configuration files: " ) ;
debug ( " http://wiki.scummvm.org/index.php/User_Manual/Configuring_ScummVM#Using_the_configuration_file_to_configure_ScummVM \n " ) ;
}
} // End of namespace GoogleDrive
} // End of namespace Cloud