2020-10-29 06:07:56 +01:00
|
|
|
class Gpicker{
|
2022-02-11 15:28:22 +01:00
|
|
|
constructor(...args){
|
|
|
|
this.init(...args)
|
|
|
|
}
|
|
|
|
init(){
|
2020-10-29 06:07:56 +01:00
|
|
|
this.apiKey = gameConfig.google_credentials.api_key
|
|
|
|
this.oauthClientId = gameConfig.google_credentials.oauth_client_id
|
|
|
|
this.projectNumber = gameConfig.google_credentials.project_number
|
|
|
|
this.scope = "https://www.googleapis.com/auth/drive.readonly"
|
|
|
|
this.folder = "application/vnd.google-apps.folder"
|
|
|
|
this.filesUrl = "https://www.googleapis.com/drive/v3/files/"
|
|
|
|
this.resolveQueue = []
|
|
|
|
this.queueActive = false
|
2022-03-11 15:34:00 +01:00
|
|
|
this.clientCallbackBind = this.clientCallback.bind(this)
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
2020-11-04 01:12:46 +01:00
|
|
|
browse(lockedCallback, errorCallback){
|
2022-07-15 16:00:43 +02:00
|
|
|
return this.loadApi(lockedCallback, errorCallback)
|
2020-11-04 01:12:46 +01:00
|
|
|
.then(() => this.getToken(lockedCallback, errorCallback))
|
2020-10-29 06:07:56 +01:00
|
|
|
.then(() => new Promise((resolve, reject) => {
|
|
|
|
this.displayPicker(data => {
|
|
|
|
if(data.action === "picked"){
|
|
|
|
var file = data.docs[0]
|
2020-11-04 01:12:46 +01:00
|
|
|
var folders = []
|
|
|
|
var rateLimit = -1
|
|
|
|
var lastBatch = 0
|
2020-10-29 06:07:56 +01:00
|
|
|
var walk = (files, output=[]) => {
|
|
|
|
for(var i = 0; i < files.length; i++){
|
|
|
|
var path = files[i].path ? files[i].path + "/" : ""
|
|
|
|
var list = files[i].list
|
2020-10-29 07:04:36 +01:00
|
|
|
if(!list){
|
|
|
|
continue
|
|
|
|
}
|
2020-10-29 06:07:56 +01:00
|
|
|
for(var j = 0; j < list.length; j++){
|
|
|
|
var file = list[j]
|
|
|
|
if(file.mimeType === this.folder){
|
2020-11-04 01:12:46 +01:00
|
|
|
folders.push({
|
|
|
|
path: path + file.name,
|
|
|
|
id: file.id
|
2020-10-29 06:07:56 +01:00
|
|
|
})
|
|
|
|
}else{
|
|
|
|
output.push(new GdriveFile({
|
|
|
|
path: path + file.name,
|
|
|
|
name: file.name,
|
|
|
|
id: file.id
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-04 01:12:46 +01:00
|
|
|
var batchList = []
|
|
|
|
for(var i = 0; i < folders.length && batchList.length < 100; i++){
|
|
|
|
if(!folders[i].listed){
|
|
|
|
folders[i].pos = i
|
|
|
|
folders[i].listed = true
|
|
|
|
batchList.push(folders[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(batchList.length){
|
|
|
|
var batch = gapi.client.newBatch()
|
|
|
|
batchList.forEach(folder => {
|
|
|
|
var req = {
|
|
|
|
q: "'" + folder.id + "' in parents and trashed = false",
|
|
|
|
orderBy: "name_natural"
|
|
|
|
}
|
|
|
|
if(folder.pageToken){
|
|
|
|
req.pageToken = folder.pageToken
|
|
|
|
}
|
|
|
|
batch.add(gapi.client.drive.files.list(req), {id: folder.pos})
|
|
|
|
})
|
|
|
|
if(lastBatch + batchList.length > 100){
|
|
|
|
var waitPromise = this.sleep(1000)
|
|
|
|
}else{
|
|
|
|
var waitPromise = Promise.resolve()
|
|
|
|
}
|
|
|
|
return waitPromise.then(() => this.queue()).then(() => batch.then(responses => {
|
2020-10-29 06:07:56 +01:00
|
|
|
var files = []
|
2020-11-04 01:12:46 +01:00
|
|
|
var rateLimited = false
|
|
|
|
for(var i in responses.result){
|
|
|
|
var result = responses.result[i].result
|
|
|
|
if(result.error){
|
|
|
|
if(result.error.errors[0].domain !== "usageLimits"){
|
|
|
|
console.warn(result)
|
|
|
|
}else if(!rateLimited){
|
|
|
|
rateLimited = true
|
|
|
|
rateLimit++
|
|
|
|
folders.push({
|
|
|
|
path: folders[i].path,
|
|
|
|
id: folders[i].id,
|
|
|
|
pageToken: folders[i].pageToken
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
if(result.nextPageToken){
|
|
|
|
folders.push({
|
|
|
|
path: folders[i].path,
|
|
|
|
id: folders[i].id,
|
|
|
|
pageToken: result.nextPageToken
|
|
|
|
})
|
|
|
|
}
|
|
|
|
files.push({path: folders[i].path, list: result.files})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(rateLimited){
|
|
|
|
return this.sleep(Math.pow(2, rateLimit) * 1000).then(() => walk(files, output))
|
|
|
|
}else{
|
|
|
|
return walk(files, output)
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
|
|
|
}))
|
|
|
|
}else{
|
|
|
|
return output
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(file.mimeType === this.folder){
|
|
|
|
return walk([{list: [file]}]).then(resolve, reject)
|
|
|
|
}else{
|
|
|
|
return reject("cancel")
|
|
|
|
}
|
|
|
|
}else if(data.action === "cancel"){
|
|
|
|
return reject("cancel")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}))
|
|
|
|
}
|
2022-07-15 16:00:43 +02:00
|
|
|
loadApi(lockedCallback=()=>{}, errorCallback=()=>{}){
|
2020-10-29 06:07:56 +01:00
|
|
|
if(window.gapi && gapi.client && gapi.client.drive){
|
2020-10-29 07:04:36 +01:00
|
|
|
return Promise.resolve()
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
2022-03-11 15:34:00 +01:00
|
|
|
var promises = [
|
|
|
|
loader.loadScript("https://apis.google.com/js/api.js"),
|
|
|
|
loader.loadScript("https://accounts.google.com/gsi/client")
|
|
|
|
]
|
2022-07-15 16:00:43 +02:00
|
|
|
var apiLoaded = false
|
2022-03-11 15:34:00 +01:00
|
|
|
return Promise.all(promises).then(() => new Promise((resolve, reject) =>
|
|
|
|
gapi.load("picker:client", {
|
2020-10-29 06:07:56 +01:00
|
|
|
callback: resolve,
|
|
|
|
onerror: reject
|
|
|
|
})
|
|
|
|
))
|
2022-07-15 16:00:43 +02:00
|
|
|
.then(() => new Promise((resolve, reject) => {
|
|
|
|
setTimeout(() => {
|
|
|
|
if(!apiLoaded){
|
|
|
|
lockedCallback(false)
|
|
|
|
}
|
|
|
|
}, 3000)
|
|
|
|
return gapi.client.load("drive", "v3").then(resolve, reject)
|
|
|
|
})).then(() => {
|
|
|
|
apiLoaded = true
|
|
|
|
lockedCallback(true)
|
|
|
|
}).catch(e => {
|
|
|
|
errorCallback(Array.isArray(e) ? e[0] : e)
|
|
|
|
return Promise.reject("cancel")
|
|
|
|
})
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
2022-03-11 15:34:00 +01:00
|
|
|
getClient(errorCallback=()=>{}, force){
|
|
|
|
var obj = {
|
|
|
|
client_id: this.oauthClientId,
|
|
|
|
scope: this.scope,
|
|
|
|
callback: this.clientCallbackBind
|
|
|
|
}
|
|
|
|
if(force){
|
|
|
|
if(!this.clientForce){
|
|
|
|
obj.select_account = true
|
|
|
|
this.clientForce = google.accounts.oauth2.initTokenClient(obj)
|
|
|
|
}
|
|
|
|
return this.clientForce
|
2020-10-29 06:07:56 +01:00
|
|
|
}else{
|
2022-03-11 15:34:00 +01:00
|
|
|
if(!this.client){
|
|
|
|
this.client = google.accounts.oauth2.initTokenClient(obj)
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
}
|
|
|
|
}
|
|
|
|
clientCallback(tokenResponse){
|
|
|
|
this.tokenResponse = tokenResponse
|
2022-03-16 07:55:25 +01:00
|
|
|
this.oauthToken = tokenResponse && tokenResponse.access_token
|
2022-03-11 15:34:00 +01:00
|
|
|
if(this.oauthToken && this.tokenResolve){
|
|
|
|
this.tokenResolve()
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
2020-12-21 14:02:56 +01:00
|
|
|
}
|
|
|
|
getToken(lockedCallback=()=>{}, errorCallback=()=>{}, force){
|
|
|
|
if(this.oauthToken && !force){
|
|
|
|
return Promise.resolve()
|
|
|
|
}
|
2022-03-11 15:34:00 +01:00
|
|
|
var client = this.getClient(errorCallback, force)
|
|
|
|
var promise = new Promise(resolve => {
|
|
|
|
this.tokenResolve = resolve
|
|
|
|
})
|
|
|
|
lockedCallback(false)
|
|
|
|
client.requestAccessToken()
|
|
|
|
return promise.then(() => {
|
|
|
|
this.tokenResolve = null
|
2022-03-11 12:20:22 +01:00
|
|
|
if(this.checkScope()){
|
|
|
|
lockedCallback(true)
|
|
|
|
}else{
|
|
|
|
return Promise.reject("cancel")
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2022-03-11 12:20:22 +01:00
|
|
|
checkScope(){
|
2022-03-11 15:34:00 +01:00
|
|
|
return google.accounts.oauth2.hasGrantedAnyScope(this.tokenResponse, this.scope)
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
2020-12-21 14:02:56 +01:00
|
|
|
switchAccounts(lockedCallback, errorCallback){
|
|
|
|
return this.loadApi().then(() => this.getToken(lockedCallback, errorCallback, true))
|
|
|
|
}
|
2020-10-29 06:07:56 +01:00
|
|
|
displayPicker(callback){
|
|
|
|
var picker = gapi.picker.api
|
|
|
|
new picker.PickerBuilder()
|
|
|
|
.setDeveloperKey(this.apiKey)
|
|
|
|
.setAppId(this.projectNumber)
|
|
|
|
.setOAuthToken(this.oauthToken)
|
2020-11-04 01:12:46 +01:00
|
|
|
.setLocale(strings.gpicker.locale)
|
2020-10-29 06:07:56 +01:00
|
|
|
.hideTitleBar()
|
|
|
|
.addView(new picker.DocsView("folders")
|
|
|
|
.setLabel(strings.gpicker.myDrive)
|
|
|
|
.setParent("root")
|
|
|
|
.setSelectFolderEnabled(true)
|
|
|
|
.setMode("grid")
|
|
|
|
)
|
|
|
|
.addView(new picker.DocsView("folders")
|
|
|
|
.setLabel(strings.gpicker.starred)
|
|
|
|
.setStarred(true)
|
|
|
|
.setSelectFolderEnabled(true)
|
|
|
|
.setMode("grid")
|
|
|
|
)
|
|
|
|
.addView(new picker.DocsView("folders")
|
|
|
|
.setLabel(strings.gpicker.sharedWithMe)
|
|
|
|
.setOwnedByMe(false)
|
|
|
|
.setSelectFolderEnabled(true)
|
|
|
|
.setMode("list")
|
|
|
|
)
|
|
|
|
.setCallback(callback)
|
|
|
|
.setSize(Infinity, Infinity)
|
|
|
|
.build()
|
|
|
|
.setVisible(true)
|
|
|
|
}
|
2022-03-16 07:55:25 +01:00
|
|
|
downloadFile(id, responseType, retry){
|
2020-10-31 12:47:42 +01:00
|
|
|
var url = this.filesUrl + id + "?alt=media"
|
|
|
|
return this.queue().then(this.getToken.bind(this)).then(() =>
|
|
|
|
loader.ajax(url, request => {
|
2022-03-16 07:55:25 +01:00
|
|
|
if(responseType){
|
|
|
|
request.responseType = responseType
|
2020-10-29 06:07:56 +01:00
|
|
|
}
|
|
|
|
request.setRequestHeader("Authorization", "Bearer " + this.oauthToken)
|
2020-10-31 12:47:42 +01:00
|
|
|
}, true).then(event => {
|
|
|
|
var request = event.target
|
|
|
|
var reject = () => Promise.reject(`${url} (${request.status})`)
|
|
|
|
if(request.status === 200){
|
|
|
|
return request.response
|
|
|
|
}else if(request.status === 401 && !retry){
|
|
|
|
return new Response(request.response).json().then(response => {
|
|
|
|
var e = response.error
|
|
|
|
if(e && e.errors[0].reason === "authError"){
|
|
|
|
delete this.oauthToken
|
2022-03-16 07:55:25 +01:00
|
|
|
return this.downloadFile(id, responseType, true)
|
2020-10-31 12:47:42 +01:00
|
|
|
}else{
|
|
|
|
return reject()
|
|
|
|
}
|
|
|
|
}, reject)
|
|
|
|
}
|
|
|
|
return reject()
|
2020-10-29 06:07:56 +01:00
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2020-11-04 01:12:46 +01:00
|
|
|
sleep(time){
|
|
|
|
return new Promise(resolve => setTimeout(resolve, time))
|
|
|
|
}
|
2020-10-29 06:07:56 +01:00
|
|
|
queue(){
|
|
|
|
return new Promise(resolve => {
|
|
|
|
this.resolveQueue.push(resolve)
|
|
|
|
if(!this.queueActive){
|
|
|
|
this.queueActive = true
|
|
|
|
this.queueTimer = setInterval(this.parseQueue.bind(this), 100)
|
|
|
|
this.parseQueue()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
parseQueue(){
|
|
|
|
if(this.resolveQueue.length){
|
|
|
|
var resolve = this.resolveQueue.shift()
|
|
|
|
resolve()
|
|
|
|
}else{
|
|
|
|
this.queueActive = false
|
|
|
|
clearInterval(this.queueTimer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|