implementing google drive sync

This commit is contained in:
Mariotaku Lee 2017-01-22 00:33:20 +08:00
parent 371fd70b24
commit 077184deae
No known key found for this signature in database
GPG Key ID: 9C0706AE47FCE2AD
17 changed files with 482 additions and 114 deletions

View File

@ -99,11 +99,15 @@ dependencies {
googleCompile "com.google.android.gms:play-services-maps:$play_services_version"
googleCompile "com.google.android.gms:play-services-auth:$play_services_version"
googleCompile 'com.google.maps.android:android-maps-utils:0.4.4'
googleCompile("com.crashlytics.sdk.android:crashlytics:$crashlyrics_version@aar") { transitive = true }
googleCompile("com.crashlytics.sdk.android:crashlytics:$crashlyrics_version@aar") {
transitive = true
}
googleCompile 'com.anjlab.android.iab.v3:library:1.0.38'
googleCompile "com.dropbox.core:dropbox-core-sdk:$dropbox_core_sdk_version"
googleCompile ':YouTubeAndroidPlayerApi:1.2.2@jar'
googleCompile "com.google.apis:google-api-services-drive:$google_api_drive_version"
googleCompile("com.google.apis:google-api-services-drive:$google_api_drive_version") {
exclude group: 'org.apache.httpcomponents'
}
// END Non-FOSS component
fdroidCompile 'org.osmdroid:osmdroid-android:5.5:release@aar'

View File

@ -1,13 +1,16 @@
package org.mariotaku.twidere.activity.sync
import android.accounts.Account
import android.accounts.AccountManager
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.google.android.gms.auth.GoogleAuthUtil
import com.google.android.gms.auth.UserRecoverableAuthException
import com.google.android.gms.common.AccountPicker
import com.google.android.gms.auth.api.Auth
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.common.api.Scope
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.drive.DriveScopes
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.successUi
@ -17,51 +20,86 @@ import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.model.sync.GoogleDriveSyncProviderInfo
class GoogleDriveAuthActivity : BaseActivity() {
class GoogleDriveAuthActivity : BaseActivity(), GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private lateinit var googleApiClient: GoogleApiClient
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = AccountPicker.newChooseAccountIntent(null, null,
arrayOf(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE), false, null, null, null, null)
startActivityForResult(intent, REQUEST_CODE_CHOOSE_ACCOUNT)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(Scope(DriveScopes.DRIVE), Scope(DriveScopes.DRIVE_METADATA))
.requestServerAuthCode(GoogleDriveSyncProviderInfo.WEB_CLIENT_ID, true)
.build()
googleApiClient = GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
googleApiClient.connect();
}
override fun onDestroy() {
googleApiClient.disconnect()
super.onDestroy()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CODE_CHOOSE_ACCOUNT -> {
if (resultCode == Activity.RESULT_OK && data != null) {
val name = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
val type = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE)
val account = Account(name, type)
REQUEST_RESOLVE_ERROR -> {
if (!googleApiClient.isConnected && !googleApiClient.isConnecting) {
googleApiClient.connect()
}
}
REQUEST_GOOGLE_SIGN_IN -> {
if (resultCode == Activity.RESULT_OK) {
val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
val authCode = result.signInAccount?.serverAuthCode ?: return
val httpTransport = NetHttpTransport()
val jsonFactory = JacksonFactory.getDefaultInstance()
val tokenRequest = GoogleAuthorizationCodeTokenRequest(httpTransport, jsonFactory,
"https://www.googleapis.com/oauth2/v4/token", GoogleDriveSyncProviderInfo.WEB_CLIENT_ID,
GoogleDriveSyncProviderInfo.WEB_CLIENT_SECRET, authCode, "")
task {
return@task GoogleAuthUtil.getToken(this, account, "oauth2:${DriveScopes.DRIVE_APPDATA}")
}.successUi { accessToken ->
preferences[dataSyncProviderInfoKey] = GoogleDriveSyncProviderInfo(accessToken)
tokenRequest.execute()
}.successUi { response ->
preferences[dataSyncProviderInfoKey] = GoogleDriveSyncProviderInfo(response.refreshToken)
setResult(Activity.RESULT_OK)
finish()
}.fail { ex ->
if (ex is UserRecoverableAuthException) {
startActivityForResult(ex.intent, REQUEST_CODE_AUTH_ERROR_RECOVER)
} else {
finish()
}
ex.printStackTrace()
}
}
}
REQUEST_CODE_AUTH_ERROR_RECOVER -> {
if (resultCode == Activity.RESULT_OK && data != null) {
val name = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
val type = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE)
val token = data.getStringExtra(AccountManager.KEY_AUTHTOKEN)
preferences[dataSyncProviderInfoKey] = GoogleDriveSyncProviderInfo(token)
}
finish()
}
}
}
override fun onConnected(connectionHint: Bundle?) {
Auth.GoogleSignInApi.signOut(googleApiClient).setResultCallback {
// Start sign in
val signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient)
startActivityForResult(signInIntent, REQUEST_GOOGLE_SIGN_IN)
}
}
override fun onConnectionSuspended(cause: Int) {
}
override fun onConnectionFailed(connectionResult: ConnectionResult) {
if (connectionResult.hasResolution()) {
connectionResult.startResolutionForResult(this, REQUEST_RESOLVE_ERROR)
} else {
}
}
companion object {
private const val REQUEST_CODE_CHOOSE_ACCOUNT: Int = 101
private const val REQUEST_CODE_AUTH_ERROR_RECOVER: Int = 102
private const val REQUEST_RESOLVE_ERROR: Int = 101
private const val REQUEST_GOOGLE_SIGN_IN: Int = 102
}
}

View File

@ -5,21 +5,24 @@ import android.content.SharedPreferences
import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.google.GoogleDriveSyncTaskRunner
class GoogleDriveSyncProviderInfo(val accessToken: String) : SyncProviderInfo(GoogleDriveSyncProviderInfo.TYPE) {
class GoogleDriveSyncProviderInfo(val refreshToken: String) : SyncProviderInfo(GoogleDriveSyncProviderInfo.TYPE) {
override fun writeToPreferences(editor: SharedPreferences.Editor) {
editor.putString(KEY_GOOGLE_DRIVE_AUTH_TOKEN, accessToken)
editor.putString(KEY_GOOGLE_DRIVE_REFRESH_TOKEN, refreshToken)
}
override fun newSyncTaskRunner(context: Context): SyncTaskRunner {
return GoogleDriveSyncTaskRunner(context, accessToken)
return GoogleDriveSyncTaskRunner(context, refreshToken)
}
companion object {
const val TYPE = "google_drive"
private const val KEY_GOOGLE_DRIVE_AUTH_TOKEN = "google_drive_auth_token"
private const val KEY_GOOGLE_DRIVE_REFRESH_TOKEN = "google_drive_refresh_token"
const val WEB_CLIENT_ID = "223623398518-0sc2i5fsqliidcdoogn53iqltpktfnff.apps.googleusercontent.com"
const val WEB_CLIENT_SECRET = "BsZ0a06UgJf5hJOTI3fcxI2u"
fun newInstance(preferences: SharedPreferences): GoogleDriveSyncProviderInfo? {
val accessToken = preferences.getString(KEY_GOOGLE_DRIVE_AUTH_TOKEN, null) ?: return null
val accessToken = preferences.getString(KEY_GOOGLE_DRIVE_REFRESH_TOKEN, null) ?: return null
return GoogleDriveSyncProviderInfo(accessToken)
}
}

View File

@ -15,7 +15,7 @@ import org.mariotaku.twidere.util.sync.FileBasedDraftsSyncAction
import java.io.IOException
import java.util.*
class DropboxDraftsSyncAction(
internal class DropboxDraftsSyncAction(
context: Context,
val client: DbxClientV2
) : FileBasedDraftsSyncAction<FileMetadata>(context) {

View File

@ -0,0 +1,15 @@
package org.mariotaku.twidere.util.sync.google
import java.io.Closeable
/**
* Created by mariotaku on 1/21/17.
*/
internal class CloseableAny<T>(val obj: T) : Closeable {
override fun close() {
if (obj is Closeable) {
obj.close()
}
}
}

View File

@ -0,0 +1,5 @@
package org.mariotaku.twidere.util.sync.google
import java.util.*
internal data class DriveFileInfo(val fileId: String, val name: String, val modifiedDate: Date)

View File

@ -3,68 +3,62 @@ package org.mariotaku.twidere.util.sync.google
import android.content.Context
import com.google.api.client.util.DateTime
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import org.mariotaku.twidere.extension.model.filename
import org.mariotaku.twidere.extension.model.readMimeMessageFrom
import org.mariotaku.twidere.extension.model.writeMimeMessageTo
import org.mariotaku.twidere.model.Draft
import org.mariotaku.twidere.util.io.DirectByteArrayOutputStream
import org.mariotaku.twidere.util.sync.FileBasedDraftsSyncAction
import java.io.IOException
import java.util.*
class GoogleDriveDraftsSyncAction(
internal class GoogleDriveDraftsSyncAction(
context: Context,
val drive: Drive
) : FileBasedDraftsSyncAction<GoogleDriveDraftsSyncAction.DriveFileInfo>(context) {
) : FileBasedDraftsSyncAction<DriveFileInfo>(context) {
val draftsDirName = "Drafts"
val draftMimeType = "message/rfc822"
private lateinit var folderId: String
private val files = drive.files()
@Throws(IOException::class)
override fun Draft.saveToRemote(): DriveFileInfo {
try {
val filename = "/Drafts/$filename"
val modifiedTime = DateTime(timestamp)
val create = drive.files().create(File().setOriginalFilename(filename).setModifiedTime(modifiedTime))
val file = create.execute()
return DriveFileInfo(file.id, file.originalFilename, Date(timestamp))
} catch (e: Exception) {
throw IOException(e)
}
val os = DirectByteArrayOutputStream()
this.writeMimeMessageTo(context, os)
val file = files.updateOrCreate(filename, draftMimeType, folderId, stream = os.inputStream(true), fileConfig = {
it.modifiedTime = DateTime(timestamp)
})
return DriveFileInfo(file.id, file.name, Date(timestamp))
}
@Throws(IOException::class)
override fun Draft.loadFromRemote(info: DriveFileInfo): Boolean {
try {
val get = drive.files().get(info.fileId)
get.executeAsInputStream().use {
val parsed = this.readMimeMessageFrom(context, it)
if (parsed) {
this.timestamp = info.draftTimestamp
this.unique_id = info.draftFileName.substringBeforeLast(".eml")
}
return parsed
val get = files.get(info.fileId)
get.executeAsInputStream().use {
val parsed = this.readMimeMessageFrom(context, it)
if (parsed) {
this.timestamp = info.draftTimestamp
this.unique_id = info.draftFileName.substringBeforeLast(".eml")
}
} catch (e: Exception) {
throw IOException(e)
return parsed
}
}
@Throws(IOException::class)
override fun removeDrafts(list: List<DriveFileInfo>): Boolean {
try {
list.forEach { info ->
drive.files().delete(info.fileId).execute()
}
return true
} catch (e: Exception) {
throw IOException(e)
list.forEach { info ->
files.delete(info.fileId).execute()
}
return true
}
@Throws(IOException::class)
override fun removeDraft(info: DriveFileInfo): Boolean {
try {
drive.files().delete(info.fileId).execute()
return true
} catch (e: Exception) {
throw IOException(e)
}
files.delete(info.fileId).execute()
return true
}
override val DriveFileInfo.draftTimestamp: Long get() = this.modifiedDate.time
@ -74,17 +68,25 @@ class GoogleDriveDraftsSyncAction(
@Throws(IOException::class)
override fun listRemoteDrafts(): List<DriveFileInfo> {
val result = ArrayList<DriveFileInfo>()
try {
val list = drive.files().list()
list.execute().files.mapTo(result) { file ->
DriveFileInfo(file.id, file.originalFilename, Date(file.modifiedTime.value))
var pageToken: String?
do {
val executeResult = files.list().apply {
q = "'$folderId' in parents and mimeType = '$draftMimeType' and trashed = false"
}.execute()
executeResult.files.filter { file ->
file.mimeType == draftMimeType
}.mapTo(result) { file ->
val lastModified = file.modifiedTime ?: file.createdTime
DriveFileInfo(file.id, file.name, Date(lastModified?.value ?: 0))
}
} catch (e: Exception) {
throw IOException(e)
}
pageToken = executeResult.nextPageToken
} while (pageToken != null)
return result
}
data class DriveFileInfo(val fileId: String, val name: String, val modifiedDate: Date)
override fun setup(): Boolean {
folderId = files.getOrCreate(draftsDirName, folderMimeType).id
return true
}
}

View File

@ -0,0 +1,67 @@
package org.mariotaku.twidere.util.sync.google
import android.content.Context
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import org.mariotaku.twidere.extension.model.initFields
import org.mariotaku.twidere.extension.model.parse
import org.mariotaku.twidere.extension.model.serialize
import org.mariotaku.twidere.extension.newPullParser
import org.mariotaku.twidere.extension.newSerializer
import org.mariotaku.twidere.model.FiltersData
import org.mariotaku.twidere.util.io.DirectByteArrayOutputStream
import org.mariotaku.twidere.util.sync.FileBasedFiltersDataSyncAction
import java.io.FileNotFoundException
import java.io.InputStream
internal class GoogleDriveFiltersDataSyncAction(
context: Context,
val drive: Drive
) : FileBasedFiltersDataSyncAction<CloseableAny<File>, GoogleDriveUploadSession<FiltersData>>(context) {
private val fileName = "filters.xml"
private lateinit var commonFolderId: String
private val files = drive.files()
override fun newLoadFromRemoteSession(): CloseableAny<File> {
val file = files.getOrNull(fileName, xmlMimeType, commonFolderId) ?: throw FileNotFoundException()
return CloseableAny(file)
}
override fun CloseableAny<File>.getRemoteLastModified(): Long {
return (obj.modifiedTime ?: obj.createdTime)?.value ?: 0
}
override fun CloseableAny<File>.loadFromRemote(): FiltersData {
val data = FiltersData()
data.parse(files.get(obj.id).executeAsInputStream().newPullParser(charset = Charsets.UTF_8))
data.initFields()
return data
}
override fun GoogleDriveUploadSession<FiltersData>.setRemoteLastModified(lastModified: Long) {
this.localModifiedTime = lastModified
}
override fun GoogleDriveUploadSession<FiltersData>.saveToRemote(data: FiltersData): Boolean {
return this.uploadData(data)
}
override fun newSaveToRemoteSession(): GoogleDriveUploadSession<FiltersData> {
return object : GoogleDriveUploadSession<FiltersData>(fileName, commonFolderId, xmlMimeType, files) {
override fun FiltersData.toInputStream(): InputStream {
val os = DirectByteArrayOutputStream()
this.serialize(os.newSerializer(charset = Charsets.UTF_8, indent = true))
return os.inputStream(true)
}
}
}
override fun setup(): Boolean {
commonFolderId = files.getOrCreate("Common", folderMimeType).id
return true
}
}

View File

@ -0,0 +1,72 @@
package org.mariotaku.twidere.util.sync.google
import android.content.Context
import android.content.SharedPreferences
import com.dropbox.core.DbxDownloader
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.FileMetadata
import com.dropbox.core.v2.files.UploadUploader
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import org.mariotaku.twidere.extension.model.serialize
import org.mariotaku.twidere.extension.newPullParser
import org.mariotaku.twidere.extension.newSerializer
import org.mariotaku.twidere.model.FiltersData
import org.mariotaku.twidere.util.io.DirectByteArrayOutputStream
import org.mariotaku.twidere.util.sync.FileBasedPreferencesValuesSyncAction
import java.io.FileNotFoundException
import java.io.InputStream
import java.util.*
internal class GoogleDrivePreferencesValuesSyncAction(
context: Context,
val drive: Drive,
preferences: SharedPreferences,
processor: Processor,
val fileName: String
) : FileBasedPreferencesValuesSyncAction<CloseableAny<File>,
GoogleDriveUploadSession<Map<String, String>>>(context, preferences, processor) {
private lateinit var commonFolderId: String
private val files = drive.files()
override fun newLoadFromRemoteSession(): CloseableAny<File> {
val file = files.getOrNull(fileName, xmlMimeType, commonFolderId) ?: throw FileNotFoundException()
return CloseableAny(file)
}
override fun CloseableAny<File>.getRemoteLastModified(): Long {
return (obj.modifiedTime ?: obj.createdTime)?.value ?: 0
}
override fun CloseableAny<File>.loadFromRemote(): MutableMap<String, String> {
val data = HashMap<String, String>()
data.parse(files.get(obj.id).executeAsInputStream().newPullParser())
return data
}
override fun newSaveToRemoteSession(): GoogleDriveUploadSession<Map<String, String>> {
return object : GoogleDriveUploadSession<Map<String, String>>(fileName, commonFolderId, xmlMimeType, files) {
override fun Map<String, String>.toInputStream(): InputStream {
val os = DirectByteArrayOutputStream()
this.serialize(os.newSerializer(charset = Charsets.UTF_8, indent = true))
return os.inputStream(true)
}
}
}
override fun GoogleDriveUploadSession<Map<String, String>>.saveToRemote(data: MutableMap<String, String>): Boolean {
return this.uploadData(data)
}
override fun GoogleDriveUploadSession<Map<String, String>>.setRemoteLastModified(lastModified: Long) {
this.localModifiedTime = lastModified
}
override fun setup(): Boolean {
commonFolderId = files.getOrCreate("Common", folderMimeType).id
return true
}
}

View File

@ -0,0 +1,82 @@
package org.mariotaku.twidere.util.sync.google
import com.google.api.client.googleapis.json.GoogleJsonResponseException
import com.google.api.client.http.InputStreamContent
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import java.io.InputStream
/**
* Created by mariotaku on 1/21/17.
*/
internal const val folderMimeType = "application/vnd.google-apps.folder"
internal const val xmlMimeType = "application/xml"
internal fun Drive.Files.getOrNull(name: String, mimeType: String?, parent: String? = "root",
trashed: Boolean = false): File? {
val find = list()
var query = "name = '$name'"
if (parent != null) {
query += " and '$parent' in parents"
}
if (mimeType != null) {
query += " and mimeType = '$mimeType'"
}
query += " and trashed = $trashed"
find.q = query
try {
return find.execute().files.firstOrNull()
} catch (e: GoogleJsonResponseException) {
if (e.statusCode == 404) {
return null
} else {
throw e
}
}
}
internal fun Drive.Files.getOrCreate(name: String, mimeType: String, parent: String = "root",
trashed: Boolean = false): File {
return getOrNull(name, mimeType, parent, trashed) ?: run {
val fileMetadata = File()
fileMetadata.name = name
fileMetadata.mimeType = mimeType
fileMetadata.parents = listOf(parent)
return@run create(fileMetadata).execute()
}
}
internal fun Drive.Files.updateOrCreate(
name: String,
mimeType: String,
parent: String = "root",
trashed: Boolean = false,
stream: InputStream,
fileConfig: ((file: File) -> Unit)? = null
): File {
return run {
val find = list()
find.q = "name = '$name' and '$parent' in parents and mimeType = '$mimeType' and trashed = $trashed"
try {
val file = find.execute().files.firstOrNull() ?: return@run null
fileConfig?.invoke(file)
return@run update(file.id, file, InputStreamContent(mimeType, stream)).execute()
} catch (e: GoogleJsonResponseException) {
if (e.statusCode == 404) {
return@run null
} else {
throw e
}
}
} ?: run {
val file = File()
file.name = name
file.mimeType = mimeType
file.parents = listOf(parent)
fileConfig?.invoke(file)
return@run create(file, InputStreamContent(mimeType, stream)).execute()
}
}

View File

@ -8,27 +8,42 @@ import com.google.api.services.drive.Drive
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.twidere.model.sync.GoogleDriveSyncProviderInfo
import org.mariotaku.twidere.util.TaskServiceRunner
import org.mariotaku.twidere.util.sync.ISyncAction
import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.UserColorsSyncProcessor
import org.mariotaku.twidere.util.sync.UserNicknamesSyncProcessor
import org.mariotaku.twidere.util.sync.dropbox.DropboxPreferencesValuesSyncAction
/**
* Created by mariotaku on 2017/1/6.
*/
class GoogleDriveSyncTaskRunner(context: Context, val accessToken: String) : SyncTaskRunner(context) {
class GoogleDriveSyncTaskRunner(context: Context, val refreshToken: String) : SyncTaskRunner(context) {
override fun onRunningTask(action: String, callback: (Boolean) -> Unit): Boolean {
val transport = NetHttpTransport.Builder().build()
val credential = GoogleCredential().setAccessToken(accessToken)
val drive = Drive.Builder(transport, JacksonFactory.getDefaultInstance(), credential).build()
val httpTransport = NetHttpTransport.Builder().build()
val jsonFactory = JacksonFactory.getDefaultInstance()
val credential = GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setClientSecrets(GoogleDriveSyncProviderInfo.WEB_CLIENT_ID, GoogleDriveSyncProviderInfo.WEB_CLIENT_SECRET)
.build()
credential.refreshToken = refreshToken
val drive = Drive.Builder(httpTransport, JacksonFactory.getDefaultInstance(), credential).build()
val syncAction: ISyncAction = when (action) {
TaskServiceRunner.ACTION_SYNC_DRAFTS -> GoogleDriveDraftsSyncAction(context, drive)
TaskServiceRunner.ACTION_SYNC_FILTERS -> GoogleDriveFiltersDataSyncAction(context, drive)
TaskServiceRunner.ACTION_SYNC_USER_COLORS -> GoogleDrivePreferencesValuesSyncAction(context,
drive, userColorNameManager.colorPreferences, UserColorsSyncProcessor,
"user_colors.xml")
TaskServiceRunner.ACTION_SYNC_USER_NICKNAMES -> GoogleDrivePreferencesValuesSyncAction(context,
drive, userColorNameManager.nicknamePreferences, UserNicknamesSyncProcessor,
"user_nicknames.xml")
else -> null
} ?: return false
task {
val about = drive.about().get().execute()
println(about)
syncAction.execute()
}.successUi {
callback(true)

View File

@ -0,0 +1,34 @@
package org.mariotaku.twidere.util.sync.google
import com.dropbox.core.v2.files.UploadUploader
import com.google.api.client.util.DateTime
import com.google.api.services.drive.Drive
import java.io.Closeable
import java.io.IOException
import java.io.InputStream
abstract internal class GoogleDriveUploadSession<in Data>(
val name: String,
val parentId: String,
val mimeType: String,
val files: Drive.Files
) : Closeable {
private var uploader: UploadUploader? = null
var localModifiedTime: Long = 0
override fun close() {
uploader?.close()
}
@Throws(IOException::class)
abstract fun Data.toInputStream(): InputStream
fun uploadData(data: Data): Boolean {
files.updateOrCreate(name, mimeType, parentId, stream = data.toInputStream(), fileConfig = {
it.modifiedTime = DateTime(localModifiedTime)
})
return true
}
}

View File

@ -42,6 +42,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Drafts
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.io.ContentLengthInputStream
import org.mariotaku.twidere.util.io.DirectByteArrayOutputStream
import java.io.*
import java.util.*
import java.util.concurrent.TimeUnit
@ -744,27 +745,7 @@ class UpdateStatusTask(
return Pair(FileBody(cis, "attachment", cis.length(), contentType), size)
}
internal class DirectByteArrayOutputStream : ByteArrayOutputStream {
constructor() : super()
constructor(size: Int) : super(size)
fun inputStream(close: Boolean): InputStream {
return DirectInputStream(this, close)
}
internal class DirectInputStream(
val os: DirectByteArrayOutputStream,
val close: Boolean
) : ByteArrayInputStream(os.buf, 0, os.count) {
override fun close() {
if (close) {
os.close()
}
super.close()
}
}
}
}
}

View File

@ -0,0 +1,24 @@
package org.mariotaku.twidere.util.io
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
class DirectByteArrayOutputStream : ByteArrayOutputStream() {
fun inputStream(close: Boolean): InputStream {
return DirectInputStream(this, close)
}
internal class DirectInputStream(
val os: DirectByteArrayOutputStream,
val close: Boolean
) : ByteArrayInputStream(os.buf, 0, os.count) {
override fun close() {
if (close) {
os.close()
}
super.close()
}
}
}

View File

@ -30,6 +30,11 @@ abstract class FileBasedDraftsSyncAction<RemoteFileInfo>(val context: Context) :
if (BuildConfig.DEBUG) {
Log.d(LOGTAG_SYNC, "Begin syncing drafts")
}
if (!setup()) {
return false
}
val syncDataDir: File = context.syncDataDir.mkdirIfNotExists() ?: return false
val snapshotsListFile = File(syncDataDir, "draft_ids.list")
@ -211,4 +216,7 @@ abstract class FileBasedDraftsSyncAction<RemoteFileInfo>(val context: Context) :
abstract val RemoteFileInfo.draftFileName: String
abstract val RemoteFileInfo.draftTimestamp: Long
@Throws(IOException::class)
open fun setup(): Boolean = true
}

View File

@ -16,6 +16,11 @@ abstract class SingleFileBasedDataSyncAction<Data, SnapshotStore, DownloadSessio
if (BuildConfig.DEBUG) {
Log.d(LOGTAG_SYNC, "Begin syncing $whatData")
}
if (!setup()) {
return false
}
val snapshotStore = newSnapshotStore()
var remoteData: Data? = null
@ -36,6 +41,9 @@ abstract class SingleFileBasedDataSyncAction<Data, SnapshotStore, DownloadSessio
}
}
} catch (e: FileNotFoundException) {
if (BuildConfig.DEBUG) {
Log.d(LOGTAG_SYNC, "Remote $whatData doesn't exists, will upload new one")
}
shouldCreateRemote = true
}
@ -148,6 +156,8 @@ abstract class SingleFileBasedDataSyncAction<Data, SnapshotStore, DownloadSessio
protected abstract fun Data.dataContentEquals(localData: Data): Boolean
protected open fun setup(): Boolean = true
protected open val whatData: String = "data"
}

View File

@ -6,6 +6,7 @@ import nl.komponents.kovenant.task
import org.mariotaku.twidere.util.TaskServiceRunner
import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import java.util.*
import javax.inject.Inject
/**
@ -54,9 +55,16 @@ abstract class SyncTaskRunner(val context: Context) {
}
fun performSync() {
TaskServiceRunner.ACTIONS_SYNC.forEach { action ->
this.runTask(action)
val actions = TaskServiceRunner.ACTIONS_SYNC.toCollection(LinkedList())
val runnable = object : Runnable {
override fun run() {
val action = actions.poll() ?: return
runTask(action) {
this.run()
}
}
}
runnable.run()
}
companion object {