Twidere-App-Android-Twitter.../twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/UpdateStatusTask.kt

771 lines
32 KiB
Kotlin
Raw Normal View History

2016-07-14 14:00:27 +02:00
package org.mariotaku.twidere.task.twitter
import android.content.ContentValues
import android.content.Context
2016-07-16 09:27:46 +02:00
import android.graphics.Bitmap
import android.graphics.BitmapFactory
2016-07-30 02:08:57 +02:00
import android.graphics.Point
2016-07-14 14:00:27 +02:00
import android.net.Uri
import android.support.annotation.UiThread
import android.support.annotation.WorkerThread
import android.text.TextUtils
import android.util.Pair
2017-01-07 18:07:12 +01:00
import com.nostra13.universalimageloader.core.DisplayImageOptions
2016-09-01 09:04:54 +02:00
import com.nostra13.universalimageloader.core.assist.ImageSize
2016-10-05 15:12:33 +02:00
import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.MediaUploadEvent
2016-07-14 14:00:27 +02:00
import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.math.NumberUtils
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.fanfou.model.PhotoStatusUpdate
import org.mariotaku.microblog.library.twitter.TwitterUpload
import org.mariotaku.microblog.library.twitter.model.*
2016-07-14 14:00:27 +02:00
import org.mariotaku.restfu.http.ContentType
import org.mariotaku.restfu.http.mime.Body
import org.mariotaku.restfu.http.mime.FileBody
import org.mariotaku.restfu.http.mime.SimpleBody
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
2016-12-03 06:48:40 +01:00
import org.mariotaku.twidere.annotation.AccountType
2016-07-14 14:00:27 +02:00
import org.mariotaku.twidere.app.TwidereApplication
2016-12-08 16:45:07 +01:00
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
2016-07-14 14:00:27 +02:00
import org.mariotaku.twidere.model.*
2017-01-03 12:59:38 +01:00
import org.mariotaku.twidere.model.analyzer.UpdateStatus
import org.mariotaku.twidere.model.draft.UpdateStatusActionExtras
2016-07-14 14:00:27 +02:00
import org.mariotaku.twidere.model.util.ParcelableLocationUtils
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
import org.mariotaku.twidere.preference.ServicePickerPreference
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
2016-07-16 09:27:46 +02:00
import java.io.*
2016-07-14 14:00:27 +02:00
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by mariotaku on 16/5/22.
*/
2016-08-17 05:40:15 +02:00
class UpdateStatusTask(
internal val context: Context,
internal val stateCallback: UpdateStatusTask.StateCallback
2016-12-15 01:13:09 +01:00
) : AbstractTask<Pair<String, ParcelableStatusUpdate>, UpdateStatusTask.UpdateStatusResult, Any?>() {
2016-07-14 14:00:27 +02:00
@Inject
lateinit var twitterWrapper: AsyncTwitterWrapper
@Inject
lateinit var preferences: SharedPreferencesWrapper
2016-09-01 09:04:54 +02:00
@Inject
lateinit var mediaLoader: MediaLoaderWrapper
2016-07-14 14:00:27 +02:00
init {
GeneralComponentHelper.build(context).inject(this)
}
override fun doLongOperation(params: Pair<String, ParcelableStatusUpdate>): UpdateStatusResult {
val draftId = saveDraft(params.first, params.second)
twitterWrapper.addSendingDraftId(draftId)
try {
2016-08-17 05:40:15 +02:00
val result = doUpdateStatus(params.second, draftId)
2016-07-14 14:00:27 +02:00
deleteOrUpdateDraft(params.second, result, draftId)
return result
} catch (e: UpdateStatusException) {
2016-08-17 05:40:15 +02:00
return UpdateStatusResult(e, draftId)
2016-07-14 14:00:27 +02:00
} finally {
twitterWrapper.removeSendingDraftId(draftId)
}
}
override fun beforeExecute() {
stateCallback.beforeExecute()
}
2016-12-15 01:13:09 +01:00
override fun afterExecute(handler: Any?, result: UpdateStatusResult) {
stateCallback.afterExecute(result)
2017-01-03 12:59:38 +01:00
if (params != null) {
logUpdateStatus(params.first, params.second, result)
}
}
private fun logUpdateStatus(actionType: String, statusUpdate: ParcelableStatusUpdate, result: UpdateStatusResult) {
val mediaType = statusUpdate.media?.firstOrNull()?.type ?: ParcelableMedia.Type.UNKNOWN
val hasLocation = statusUpdate.location != null
val preciseLocation = statusUpdate.display_coordinates
Analyzer.log(UpdateStatus(result.accountTypes.firstOrNull(), actionType, mediaType,
hasLocation, preciseLocation, result.succeed))
2016-07-14 14:00:27 +02:00
}
@Throws(UpdateStatusException::class)
2016-08-17 05:40:15 +02:00
private fun doUpdateStatus(update: ParcelableStatusUpdate, draftId: Long): UpdateStatusResult {
2016-07-14 14:00:27 +02:00
val app = TwidereApplication.getInstance(context)
val uploader = getMediaUploader(app)
val shortener = getStatusShortener(app)
val pendingUpdate = PendingStatusUpdate.from(update)
uploadMedia(uploader, update, pendingUpdate)
shortenStatus(shortener, update, pendingUpdate)
val result: UpdateStatusResult
try {
2016-08-17 05:40:15 +02:00
result = requestUpdateStatus(update, pendingUpdate, draftId)
2016-07-14 14:00:27 +02:00
} catch (e: IOException) {
2016-08-17 05:40:15 +02:00
return UpdateStatusResult(UpdateStatusException(e), draftId)
2016-07-14 14:00:27 +02:00
}
mediaUploadCallback(uploader, pendingUpdate, result)
statusShortenCallback(shortener, pendingUpdate, result)
return result
}
private fun deleteOrUpdateDraft(update: ParcelableStatusUpdate, result: UpdateStatusResult, draftId: Long) {
val where = Expression.equalsArgs(Drafts._ID).sql
val whereArgs = arrayOf(draftId.toString())
var hasError = false
val failedAccounts = ArrayList<UserKey>()
for (i in update.accounts.indices) {
val exception = result.exceptions[i]
if (exception != null && !isDuplicate(exception)) {
hasError = true
2016-12-04 04:58:03 +01:00
failedAccounts.add(update.accounts[i].key)
2016-07-14 14:00:27 +02:00
}
}
val cr = context.contentResolver
if (hasError) {
val values = ContentValues()
2016-08-25 04:10:53 +02:00
values.put(Drafts.ACCOUNT_KEYS, failedAccounts.joinToString(","))
2016-07-14 14:00:27 +02:00
cr.update(Drafts.CONTENT_URI, values, where, whereArgs)
// TODO show error message
} else {
cr.delete(Drafts.CONTENT_URI, where, whereArgs)
}
}
@Throws(UploadException::class)
private fun uploadMedia(uploader: MediaUploaderInterface?,
update: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate) {
stateCallback.onStartUploadingMedia()
if (uploader == null) {
uploadMediaWithDefaultProvider(update, pendingUpdate)
} else {
uploadMediaWithExtension(uploader, update, pendingUpdate)
}
}
@Throws(UploadException::class)
private fun uploadMediaWithExtension(uploader: MediaUploaderInterface,
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
uploader.waitForService()
2016-07-14 14:00:27 +02:00
val media: Array<UploaderMediaItem>
try {
media = UploaderMediaItem.getFromStatusUpdate(context, update)
} catch (e: FileNotFoundException) {
throw UploadException(e)
}
val sharedMedia = HashMap<UserKey, MediaUploadResult>()
for (i in 0..pending.length - 1) {
val account = update.accounts[i]
// Skip upload if shared media found
2016-12-04 04:58:03 +01:00
val accountKey = account.key
2016-07-14 14:00:27 +02:00
var uploadResult: MediaUploadResult? = sharedMedia[accountKey]
if (uploadResult == null) {
2016-12-07 15:01:27 +01:00
uploadResult = uploader.upload(update, accountKey, media) ?: run {
throw UploadException()
}
if (uploadResult.media_uris == null) {
throw UploadException(uploadResult.error_message ?: "Unknown error")
2016-07-14 14:00:27 +02:00
}
pending.mediaUploadResults[i] = uploadResult
if (uploadResult.shared_owners != null) {
for (sharedOwner in uploadResult.shared_owners) {
sharedMedia.put(sharedOwner, uploadResult)
}
}
}
// Override status text
pending.overrideTexts[i] = Utils.getMediaUploadStatus(context,
uploadResult.media_uris, pending.overrideTexts[i])
}
}
2016-12-07 15:01:27 +01:00
@Throws(UpdateStatusException::class)
2016-07-14 14:00:27 +02:00
private fun shortenStatus(shortener: StatusShortenerInterface?,
update: ParcelableStatusUpdate,
pending: PendingStatusUpdate) {
2016-12-08 08:47:17 +01:00
if (shortener == null) return
2016-07-14 14:00:27 +02:00
stateCallback.onShorteningStatus()
val sharedShortened = HashMap<UserKey, StatusShortenResult>()
2016-12-08 08:47:17 +01:00
for (i in 0 until pending.length) {
2016-07-14 14:00:27 +02:00
val account = update.accounts[i]
2016-12-08 08:47:17 +01:00
val text = pending.overrideTexts[i]
val textLimit = TwidereValidator.getTextLimit(account)
if (textLimit >= 0 && text.length <= textLimit) {
continue
}
shortener.waitForService()
2016-07-14 14:00:27 +02:00
// Skip upload if this shared media found
2016-12-04 04:58:03 +01:00
val accountKey = account.key
2016-07-14 14:00:27 +02:00
var shortenResult: StatusShortenResult? = sharedShortened[accountKey]
if (shortenResult == null) {
2016-12-08 08:47:17 +01:00
shortenResult = shortener.shorten(update, accountKey, text) ?: run {
2016-12-07 15:01:27 +01:00
throw ShortenException()
}
if (shortenResult.shortened == null) {
throw ShortenException(shortenResult.error_message ?: "Unknown error")
2016-07-14 14:00:27 +02:00
}
pending.statusShortenResults[i] = shortenResult
if (shortenResult.shared_owners != null) {
for (sharedOwner in shortenResult.shared_owners) {
sharedShortened.put(sharedOwner, shortenResult)
}
}
}
// Override status text
pending.overrideTexts[i] = shortenResult.shortened
}
}
@Throws(IOException::class)
2016-08-17 05:40:15 +02:00
private fun requestUpdateStatus(statusUpdate: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate,
draftId: Long): UpdateStatusResult {
2016-07-14 14:00:27 +02:00
stateCallback.onUpdatingStatus()
2017-01-03 12:59:38 +01:00
val result = UpdateStatusResult(pendingUpdate.length, draftId)
2016-07-14 14:00:27 +02:00
2016-08-17 05:40:15 +02:00
for (i in 0 until pendingUpdate.length) {
2016-07-14 14:00:27 +02:00
val account = statusUpdate.accounts[i]
2017-01-03 12:59:38 +01:00
result.accountTypes[i] = account.type
2016-12-06 06:15:22 +01:00
val microBlog = MicroBlogAPIFactory.getInstance(context, account.key)
2016-10-05 15:12:33 +02:00
var bodyAndSize: Pair<Body, Point>? = null
2016-07-14 14:00:27 +02:00
try {
2016-12-04 06:45:57 +01:00
when (account.type) {
2016-12-03 06:48:40 +01:00
AccountType.FANFOU -> {
2016-07-14 14:00:27 +02:00
// Call uploadPhoto if media present
if (!ArrayUtils.isEmpty(statusUpdate.media)) {
// Fanfou only allow one photo
if (statusUpdate.media.size > 1) {
result.exceptions[i] = MicroBlogException(
context.getString(R.string.error_too_many_photos_fanfou))
} else {
2016-07-30 02:08:57 +02:00
val sizeLimit = Point(2048, 1536)
2016-10-05 15:12:33 +02:00
bodyAndSize = getBodyFromMedia(context, mediaLoader,
2016-07-30 02:08:57 +02:00
Uri.parse(statusUpdate.media[0].uri),
2016-07-31 08:41:07 +02:00
sizeLimit, statusUpdate.media[0].type,
2016-07-30 02:08:57 +02:00
ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(-1, position, length)
})
2016-10-05 15:12:33 +02:00
val photoUpdate = PhotoStatusUpdate(bodyAndSize.first,
2016-07-14 14:00:27 +02:00
pendingUpdate.overrideTexts[i])
val requestResult = microBlog.uploadPhoto(photoUpdate)
result.statuses[i] = ParcelableStatusUtils.fromStatus(requestResult,
2016-12-04 04:58:03 +01:00
account.key, false)
2016-07-14 14:00:27 +02:00
}
} else {
val requestResult = twitterUpdateStatus(microBlog,
statusUpdate, pendingUpdate, pendingUpdate.overrideTexts[i], i)
result.statuses[i] = ParcelableStatusUtils.fromStatus(requestResult,
2016-12-04 04:58:03 +01:00
account.key, false)
2016-07-14 14:00:27 +02:00
}
}
else -> {
val requestResult = twitterUpdateStatus(microBlog, statusUpdate,
pendingUpdate, pendingUpdate.overrideTexts[i], i)
result.statuses[i] = ParcelableStatusUtils.fromStatus(requestResult,
2016-12-04 04:58:03 +01:00
account.key, false)
2016-07-14 14:00:27 +02:00
}
}
} catch (e: MicroBlogException) {
result.exceptions[i] = e
} finally {
2016-10-05 15:12:33 +02:00
Utils.closeSilently(bodyAndSize?.first)
2016-07-14 14:00:27 +02:00
}
}
return result
}
/**
* Calling Twitter's upload method. This method sets multiple owner for bandwidth saving
*/
@Throws(UploadException::class)
private fun uploadMediaWithDefaultProvider(update: ParcelableStatusUpdate, pendingUpdate: PendingStatusUpdate) {
// Return empty array if no media attached
if (ArrayUtils.isEmpty(update.media)) return
2016-07-31 08:41:07 +02:00
val ownersList = update.accounts.filter {
2016-12-04 06:45:57 +01:00
AccountType.TWITTER == it.type
2016-12-04 04:58:03 +01:00
}.map(AccountDetails::key)
2016-07-31 08:41:07 +02:00
val ownerIds = ownersList.map {
it.id
}.toTypedArray()
2016-07-14 14:00:27 +02:00
for (i in 0..pendingUpdate.length - 1) {
val account = update.accounts[i]
val mediaIds: Array<String>?
2016-12-04 06:45:57 +01:00
when (account.type) {
2016-12-03 06:48:40 +01:00
AccountType.TWITTER -> {
2016-12-08 16:45:07 +01:00
val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java)
2016-07-14 14:00:27 +02:00
if (pendingUpdate.sharedMediaIds != null) {
mediaIds = pendingUpdate.sharedMediaIds
} else {
mediaIds = uploadAllMediaShared(upload, update, ownerIds, true)
pendingUpdate.sharedMediaIds = mediaIds
}
}
2016-12-03 06:48:40 +01:00
AccountType.FANFOU -> {
2016-07-14 14:00:27 +02:00
// Nope, fanfou uses photo uploading API
mediaIds = null
}
2016-12-03 06:48:40 +01:00
AccountType.STATUSNET -> {
2016-07-14 14:00:27 +02:00
// TODO use their native API
2016-12-08 16:45:07 +01:00
val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java)
2016-07-14 14:00:27 +02:00
mediaIds = uploadAllMediaShared(upload, update, ownerIds, false)
}
else -> {
mediaIds = null
}
}
pendingUpdate.mediaIds[i] = mediaIds
}
pendingUpdate.sharedMediaOwners = ownersList.toTypedArray()
}
@Throws(MicroBlogException::class)
private fun twitterUpdateStatus(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate, overrideText: String,
index: Int): Status {
val status = StatusUpdate(overrideText)
if (statusUpdate.in_reply_to_status != null) {
status.inReplyToStatusId(statusUpdate.in_reply_to_status.id)
}
if (statusUpdate.repost_status_id != null) {
status.setRepostStatusId(statusUpdate.repost_status_id)
}
if (statusUpdate.attachment_url != null) {
status.setAttachmentUrl(statusUpdate.attachment_url)
}
if (statusUpdate.location != null) {
status.location(ParcelableLocationUtils.toGeoLocation(statusUpdate.location))
status.displayCoordinates(statusUpdate.display_coordinates)
}
val mediaIds = pendingUpdate.mediaIds[index]
if (mediaIds != null) {
status.mediaIds(*mediaIds)
}
status.possiblySensitive(statusUpdate.is_possibly_sensitive)
return microBlog.updateStatus(status)
}
private fun statusShortenCallback(shortener: StatusShortenerInterface?, pendingUpdate: PendingStatusUpdate, updateResult: UpdateStatusResult) {
2016-12-07 15:01:27 +01:00
if (shortener == null || !shortener.waitForService()) return
2016-07-14 14:00:27 +02:00
for (i in 0..pendingUpdate.length - 1) {
val shortenResult = pendingUpdate.statusShortenResults[i]
val status = updateResult.statuses[i]
if (shortenResult == null || status == null) continue
shortener.callback(shortenResult, status)
}
}
private fun mediaUploadCallback(uploader: MediaUploaderInterface?, pendingUpdate: PendingStatusUpdate, updateResult: UpdateStatusResult) {
2016-12-07 15:01:27 +01:00
if (uploader == null || !uploader.waitForService()) return
2016-07-14 14:00:27 +02:00
for (i in 0..pendingUpdate.length - 1) {
val uploadResult = pendingUpdate.mediaUploadResults[i]
val status = updateResult.statuses[i]
if (uploadResult == null || status == null) continue
uploader.callback(uploadResult, status)
}
}
@Throws(UploaderNotFoundException::class, UploadException::class, ShortenerNotFoundException::class, ShortenException::class)
private fun getStatusShortener(app: TwidereApplication): StatusShortenerInterface? {
val shortenerComponent = preferences.getString(KEY_STATUS_SHORTENER, null)
if (ServicePickerPreference.isNoneValue(shortenerComponent)) return null
val shortener = StatusShortenerInterface.getInstance(app, shortenerComponent) ?: throw ShortenerNotFoundException()
try {
shortener.checkService { metaData ->
if (metaData == null) throw ExtensionVersionMismatchException()
val extensionVersion = metaData.getString(METADATA_KEY_EXTENSION_VERSION_STATUS_SHORTENER)
if (!TextUtils.equals(extensionVersion, context.getString(R.string.status_shortener_service_interface_version))) {
throw ExtensionVersionMismatchException()
}
}
} catch (e: ExtensionVersionMismatchException) {
throw ShortenException(context.getString(R.string.shortener_version_incompatible))
2016-07-14 14:00:27 +02:00
} catch (e: AbsServiceInterface.CheckServiceException) {
throw ShortenException(e)
}
return shortener
}
@Throws(UploaderNotFoundException::class, UploadException::class)
private fun getMediaUploader(app: TwidereApplication): MediaUploaderInterface? {
val uploaderComponent = preferences.getString(KEY_MEDIA_UPLOADER, null)
if (ServicePickerPreference.isNoneValue(uploaderComponent)) return null
val uploader = MediaUploaderInterface.getInstance(app, uploaderComponent) ?: throw UploaderNotFoundException(context.getString(R.string.error_message_media_uploader_not_found))
try {
uploader.checkService { metaData ->
if (metaData == null) throw ExtensionVersionMismatchException()
val extensionVersion = metaData.getString(METADATA_KEY_EXTENSION_VERSION_MEDIA_UPLOADER)
if (!TextUtils.equals(extensionVersion, context.getString(R.string.media_uploader_service_interface_version))) {
throw ExtensionVersionMismatchException()
}
}
} catch (e: AbsServiceInterface.CheckServiceException) {
if (e is ExtensionVersionMismatchException) {
throw UploadException(context.getString(R.string.uploader_version_incompatible))
}
throw UploadException(e)
}
return uploader
}
@Throws(UploadException::class)
private fun uploadAllMediaShared(upload: TwitterUpload, update: ParcelableStatusUpdate,
ownerIds: Array<String>, chucked: Boolean): Array<String> {
val mediaIds = update.media.mapIndexed { index, media ->
val resp: MediaUploadResponse
//noinspection TryWithIdenticalCatches
2016-10-05 15:12:33 +02:00
var bodyAndSize: Pair<Body, Point>? = null
2016-07-14 14:00:27 +02:00
try {
2016-07-30 02:08:57 +02:00
val sizeLimit = Point(2048, 1536)
2016-10-05 15:12:33 +02:00
bodyAndSize = getBodyFromMedia(context, mediaLoader, Uri.parse(media.uri), sizeLimit,
2016-07-31 08:41:07 +02:00
media.type, ContentLengthInputStream.ReadListener { length, position ->
stateCallback.onUploadingProgressChanged(index, position, length)
})
2016-10-05 15:12:33 +02:00
val mediaUploadEvent = MediaUploadEvent.create(context, media)
mediaUploadEvent.setFileSize(bodyAndSize.first.length())
mediaUploadEvent.setGeometry(bodyAndSize.second.x, bodyAndSize.second.y)
2016-07-14 14:00:27 +02:00
if (chucked) {
2016-10-05 15:12:33 +02:00
resp = uploadMediaChucked(upload, bodyAndSize.first, ownerIds)
2016-07-14 14:00:27 +02:00
} else {
2016-10-05 15:12:33 +02:00
resp = upload.uploadMedia(bodyAndSize.first, ownerIds)
2016-07-14 14:00:27 +02:00
}
2016-10-05 15:12:33 +02:00
mediaUploadEvent.markEnd()
HotMobiLogger.getInstance(context).log(mediaUploadEvent)
2016-07-14 14:00:27 +02:00
} catch (e: IOException) {
throw UploadException(e)
} catch (e: MicroBlogException) {
throw UploadException(e)
} finally {
2016-10-05 15:12:33 +02:00
Utils.closeSilently(bodyAndSize?.first)
2016-07-14 14:00:27 +02:00
}
if (media.alt_text?.isNotEmpty() ?: false) {
try {
upload.createMetadata(NewMediaMetadata(resp.id, media.alt_text))
} catch (e: MicroBlogException) {
// Ignore
}
}
return@mapIndexed resp.id
2016-07-14 14:00:27 +02:00
}
return mediaIds.toTypedArray()
}
@Throws(IOException::class, MicroBlogException::class)
private fun uploadMediaChucked(upload: TwitterUpload, body: Body,
ownerIds: Array<String>): MediaUploadResponse {
val mediaType = body.contentType().contentType
val length = body.length()
val stream = body.stream()
var response = upload.initUploadMedia(mediaType, length, ownerIds)
val segments = if (length == 0L) 0 else (length / BULK_SIZE + 1).toInt()
for (segmentIndex in 0..segments - 1) {
val currentBulkSize = Math.min(BULK_SIZE.toLong(), length - segmentIndex * BULK_SIZE).toInt()
val bulk = SimpleBody(ContentType.OCTET_STREAM, null, currentBulkSize.toLong(),
stream)
upload.appendUploadMedia(response.id, segmentIndex, bulk)
}
response = upload.finalizeUploadMedia(response.id)
2016-08-09 09:48:16 +02:00
var info: MediaUploadResponse.ProcessingInfo? = response.processingInfo
while (info != null && shouldWaitForProcess(info)) {
val checkAfterSecs = info.checkAfterSecs
if (checkAfterSecs <= 0) {
break
2016-07-14 14:00:27 +02:00
}
2016-08-09 09:48:16 +02:00
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(checkAfterSecs))
} catch (e: InterruptedException) {
break
}
response = upload.getUploadMediaStatus(response.id)
info = response.processingInfo
2016-07-14 14:00:27 +02:00
}
if (info != null && MediaUploadResponse.ProcessingInfo.State.FAILED == info.state) {
val exception = MicroBlogException()
val errorInfo = info.error
if (errorInfo != null) {
exception.errors = arrayOf(errorInfo)
}
throw exception
}
return response
}
private fun isDuplicate(exception: Exception): Boolean {
return exception is MicroBlogException && exception.errorCode == ErrorInfo.STATUS_IS_DUPLICATE
}
2016-07-16 12:35:54 +02:00
private fun shouldWaitForProcess(info: MediaUploadResponse.ProcessingInfo): Boolean {
2016-07-14 14:00:27 +02:00
when (info.state) {
MediaUploadResponse.ProcessingInfo.State.PENDING, MediaUploadResponse.ProcessingInfo.State.IN_PROGRESS -> return true
else -> return false
}
}
2016-08-17 05:40:15 +02:00
private fun saveDraft(@Draft.Action draftAction: String?, statusUpdate: ParcelableStatusUpdate): Long {
2016-07-14 14:00:27 +02:00
val draft = Draft()
draft.unique_id = statusUpdate.draft_unique_id ?: UUID.randomUUID().toString()
2016-12-04 04:58:03 +01:00
draft.account_keys = statusUpdate.accounts.map { it.key }.toTypedArray()
2016-12-05 12:28:17 +01:00
draft.action_type = draftAction ?: Draft.Action.UPDATE_STATUS
2016-07-14 14:00:27 +02:00
draft.text = statusUpdate.text
draft.location = statusUpdate.location
draft.media = statusUpdate.media
draft.timestamp = System.currentTimeMillis()
draft.action_extras = UpdateStatusActionExtras().apply {
2016-12-05 12:28:17 +01:00
inReplyToStatus = statusUpdate.in_reply_to_status
isPossiblySensitive = statusUpdate.is_possibly_sensitive
isRepostStatusId = statusUpdate.repost_status_id
displayCoordinates = statusUpdate.display_coordinates
attachmentUrl = statusUpdate.attachment_url
}
2016-07-14 14:00:27 +02:00
val resolver = context.contentResolver
val draftUri = resolver.insert(Drafts.CONTENT_URI, DraftValuesCreator.create(draft)) ?: return -1
return NumberUtils.toLong(draftUri.lastPathSegment, -1)
}
internal class PendingStatusUpdate(val length: Int, defaultText: String) {
var sharedMediaIds: Array<String>? = null
var sharedMediaOwners: Array<UserKey>? = null
val overrideTexts: Array<String>
val mediaIds: Array<Array<String>?>
val mediaUploadResults: Array<MediaUploadResult?>
val statusShortenResults: Array<StatusShortenResult?>
init {
overrideTexts = Array(length) { idx ->
defaultText
}
mediaUploadResults = arrayOfNulls<MediaUploadResult>(length)
statusShortenResults = arrayOfNulls<StatusShortenResult>(length)
mediaIds = arrayOfNulls<Array<String>>(length)
}
companion object {
fun from(statusUpdate: ParcelableStatusUpdate): PendingStatusUpdate {
return PendingStatusUpdate(statusUpdate.accounts.size,
statusUpdate.text)
}
}
}
class UpdateStatusResult {
val statuses: Array<ParcelableStatus?>
val exceptions: Array<MicroBlogException?>
2017-01-03 12:59:38 +01:00
val accountTypes: Array<String?>
2016-07-14 14:00:27 +02:00
val exception: UpdateStatusException?
2016-08-17 05:40:15 +02:00
val draftId: Long
val succeed: Boolean get() = !statuses.contains(null)
2016-07-14 14:00:27 +02:00
2017-01-03 12:59:38 +01:00
constructor(count: Int, draftId: Long) {
this.statuses = arrayOfNulls(count)
this.exceptions = arrayOfNulls(count)
this.accountTypes = arrayOfNulls(count)
2016-07-14 14:00:27 +02:00
this.exception = null
2016-08-17 05:40:15 +02:00
this.draftId = draftId
2016-07-14 14:00:27 +02:00
}
2016-08-17 05:40:15 +02:00
constructor(exception: UpdateStatusException, draftId: Long) {
2016-07-14 14:00:27 +02:00
this.exception = exception
2017-01-03 12:59:38 +01:00
this.statuses = arrayOfNulls(0)
this.exceptions = arrayOfNulls(0)
this.accountTypes = arrayOfNulls(0)
2016-08-17 05:40:15 +02:00
this.draftId = draftId
2016-07-14 14:00:27 +02:00
}
}
open class UpdateStatusException : Exception {
2016-08-17 05:40:15 +02:00
constructor() : super()
2016-07-14 14:00:27 +02:00
2016-08-17 05:40:15 +02:00
constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable)
2016-07-14 14:00:27 +02:00
2016-08-17 05:40:15 +02:00
constructor(throwable: Throwable) : super(throwable)
2016-07-14 14:00:27 +02:00
2016-08-17 05:40:15 +02:00
constructor(message: String) : super(message)
2016-07-14 14:00:27 +02:00
}
class UploaderNotFoundException : UpdateStatusException {
2016-08-17 05:40:15 +02:00
constructor() : super()
2016-07-14 14:00:27 +02:00
2016-08-17 05:40:15 +02:00
constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable)
2016-07-14 14:00:27 +02:00
2016-08-17 05:40:15 +02:00
constructor(throwable: Throwable) : super(throwable)
2016-07-14 14:00:27 +02:00
2016-08-17 05:40:15 +02:00
constructor(message: String) : super(message)
2016-07-14 14:00:27 +02:00
}
class UploadException : UpdateStatusException {
constructor() : super() {
}
constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable) {
}
constructor(throwable: Throwable) : super(throwable) {
}
constructor(message: String) : super(message) {
}
}
class ExtensionVersionMismatchException : AbsServiceInterface.CheckServiceException()
class ShortenerNotFoundException : UpdateStatusException()
class ShortenException : UpdateStatusException {
constructor() : super() {
}
constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable) {
}
constructor(throwable: Throwable) : super(throwable) {
}
constructor(message: String) : super(message) {
}
}
interface StateCallback {
@WorkerThread
fun onStartUploadingMedia()
@WorkerThread
fun onUploadingProgressChanged(index: Int, current: Long, total: Long)
@WorkerThread
fun onShorteningStatus()
@WorkerThread
fun onUpdatingStatus()
@UiThread
2016-12-15 01:13:09 +01:00
fun afterExecute(result: UpdateStatusResult)
2016-07-14 14:00:27 +02:00
@UiThread
fun beforeExecute()
}
companion object {
private val BULK_SIZE = 256 * 1024// 128 Kib
@Throws(IOException::class)
2016-09-01 09:04:54 +02:00
fun getBodyFromMedia(context: Context, mediaLoader: MediaLoaderWrapper,
mediaUri: Uri, sizeLimit: Point? = null,
2016-07-31 08:41:07 +02:00
@ParcelableMedia.Type type: Int,
2016-10-05 15:12:33 +02:00
readListener: ContentLengthInputStream.ReadListener): Pair<Body, Point> {
2016-09-01 09:04:54 +02:00
val resolver = context.contentResolver
2016-07-31 08:41:07 +02:00
var mediaType = resolver.getType(mediaUri)
2016-10-05 15:12:33 +02:00
val size = Point()
2016-09-01 09:04:54 +02:00
val cis = run {
if (type == ParcelableMedia.Type.IMAGE && sizeLimit != null) {
val length: Long
val o = BitmapFactory.Options()
o.inJustDecodeBounds = true
BitmapFactoryUtils.decodeUri(resolver, mediaUri, null, o)
if (o.outMimeType != null) {
mediaType = o.outMimeType
}
2016-10-05 15:12:33 +02:00
size.set(o.outWidth, o.outHeight)
2016-09-01 09:04:54 +02:00
o.inSampleSize = Utils.calculateInSampleSize(o.outWidth, o.outHeight,
sizeLimit.x, sizeLimit.y)
o.inJustDecodeBounds = false
if (o.outWidth > 0 && o.outHeight > 0 && mediaType != "image/gif") {
2017-01-07 18:07:12 +01:00
val displayOptions = DisplayImageOptions.Builder()
.considerExifParams(true)
.build()
2016-09-01 09:04:54 +02:00
val bitmap = mediaLoader.loadImageSync(mediaUri.toString(),
2017-01-07 18:07:12 +01:00
ImageSize(o.outWidth, o.outHeight).scaleDown(o.inSampleSize),
displayOptions)
2016-09-01 09:04:54 +02:00
if (bitmap != null) {
2016-10-05 15:12:33 +02:00
size.set(bitmap.width, bitmap.height)
2016-09-01 09:04:54 +02:00
val os = DirectByteArrayOutputStream()
when (mediaType) {
"image/png", "image/x-png", "image/webp", "image-x-webp" -> {
bitmap.compress(Bitmap.CompressFormat.PNG, 0, os)
}
else -> {
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, os)
}
2016-08-23 18:24:51 +02:00
}
2016-09-01 09:04:54 +02:00
length = os.size().toLong()
return@run ContentLengthInputStream(os.inputStream(true), length)
2016-08-23 18:24:51 +02:00
}
}
2016-07-31 08:41:07 +02:00
}
val st = resolver.openInputStream(mediaUri) ?: throw FileNotFoundException(mediaUri.toString())
2016-09-01 09:04:54 +02:00
val length = st.available().toLong()
return@run ContentLengthInputStream(st, length)
2016-07-16 09:27:46 +02:00
}
2016-09-01 09:04:54 +02:00
2016-07-14 14:00:27 +02:00
cis.setReadListener(readListener)
val contentType: ContentType
if (TextUtils.isEmpty(mediaType)) {
contentType = ContentType.parse("application/octet-stream")
} else {
contentType = ContentType.parse(mediaType!!)
}
2016-10-05 15:12:33 +02:00
return Pair(FileBody(cis, "attachment", cis.length(), contentType), size)
2016-07-14 14:00:27 +02:00
}
2016-07-16 09:27:46 +02:00
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()
}
}
}
2016-07-14 14:00:27 +02:00
}
}