fixed save some images in fanfou

This commit is contained in:
Mariotaku Lee 2017-02-04 14:13:08 +08:00
parent 3a9c3cef8d
commit f2993fc488
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
14 changed files with 307 additions and 307 deletions

View File

@ -0,0 +1,18 @@
package org.mariotaku.twidere.annotation;
import android.support.annotation.StringDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by mariotaku on 2017/2/4.
*/
@StringDef({CacheFileType.IMAGE, CacheFileType.VIDEO, CacheFileType.JSON})
@Retention(RetentionPolicy.SOURCE)
public @interface CacheFileType {
String IMAGE = "image";
String VIDEO = "video";
String JSON = "json";
}

View File

@ -1,239 +0,0 @@
package org.mariotaku.twidere.provider;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringDef;
import android.webkit.MimeTypeMap;
import com.bluelinelabs.logansquare.JsonMapper;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import org.mariotaku.commons.logansquare.LoganSquareMapperFinder;
import org.mariotaku.restfu.RestFuUtils;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.model.CacheMetadata;
import org.mariotaku.twidere.task.SaveFileTask;
import org.mariotaku.twidere.util.BitmapUtils;
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Locale;
import javax.inject.Inject;
import okio.ByteString;
/**
* Created by mariotaku on 16/1/1.
*/
public class CacheProvider extends ContentProvider implements TwidereConstants {
@Inject
DiskCache mSimpleDiskCache;
public static Uri getCacheUri(String key, @Type String type) {
final Uri.Builder builder = new Uri.Builder();
builder.scheme(ContentResolver.SCHEME_CONTENT);
builder.authority(TwidereConstants.AUTHORITY_TWIDERE_CACHE);
builder.appendPath(ByteString.encodeUtf8(key).base64Url());
if (type != null) {
builder.appendQueryParameter(QUERY_PARAM_TYPE, type);
}
return builder.build();
}
@NonNull
public static String getCacheKey(Uri uri) {
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
throw new IllegalArgumentException(uri.toString());
if (!TwidereConstants.AUTHORITY_TWIDERE_CACHE.equals(uri.getAuthority()))
throw new IllegalArgumentException(uri.toString());
return ByteString.decodeBase64(uri.getLastPathSegment()).utf8();
}
public static String getMetadataKey(Uri uri) {
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
throw new IllegalArgumentException(uri.toString());
if (!TwidereConstants.AUTHORITY_TWIDERE_CACHE.equals(uri.getAuthority()))
throw new IllegalArgumentException(uri.toString());
return getExtraKey(ByteString.decodeBase64(uri.getLastPathSegment()).utf8());
}
public static String getExtraKey(String key) {
return key + ".extra";
}
@Override
public boolean onCreate() {
final Context context = getContext();
assert context != null;
GeneralComponentHelper.build(context).inject(this);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
final CacheMetadata metadata = getMetadata(uri);
if (metadata != null) {
return metadata.getContentType();
}
final String type = uri.getQueryParameter(QUERY_PARAM_TYPE);
if (type != null) {
switch (type) {
case Type.IMAGE: {
final File file = mSimpleDiskCache.get(getCacheKey(uri));
if (file == null) return null;
return BitmapUtils.getImageMimeType(file);
}
case Type.VIDEO: {
return "video/mp4";
}
case Type.JSON: {
return "application/json";
}
}
}
return null;
}
public CacheMetadata getMetadata(@NonNull Uri uri) {
final File file = mSimpleDiskCache.get(getMetadataKey(uri));
if (file == null) return null;
FileInputStream is = null;
try {
final JsonMapper<CacheMetadata> mapper = LoganSquareMapperFinder.mapperFor(CacheMetadata.class);
is = new FileInputStream(file);
return mapper.parse(is);
} catch (IOException e) {
return null;
} finally {
RestFuUtils.closeSilently(is);
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Nullable
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
try {
final File file = mSimpleDiskCache.get(getCacheKey(uri));
if (file == null) throw new FileNotFoundException();
final int modeBits = modeToMode(mode);
if (modeBits != ParcelFileDescriptor.MODE_READ_ONLY)
throw new IllegalArgumentException("Cache can't be opened for write");
return ParcelFileDescriptor.open(file, modeBits);
} catch (IOException e) {
throw new FileNotFoundException();
}
}
/**
* Copied from ContentResolver.java
*/
private static int modeToMode(String mode) {
int modeBits;
if ("r".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
} else if ("w".equals(mode) || "wt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else if ("wa".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_APPEND;
} else if ("rw".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE;
} else if ("rwt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else {
throw new IllegalArgumentException("Invalid mode: " + mode);
}
return modeBits;
}
public static final class CacheFileTypeCallback implements SaveFileTask.FileInfoCallback {
private final Context context;
private final String type;
public CacheFileTypeCallback(Context context, @Type String type) {
this.context = context;
this.type = type;
}
@Override
@NonNull
public String getFilename(@NonNull Uri source) {
String cacheKey = getCacheKey(source);
final int indexOfSsp = cacheKey.indexOf("://");
if (indexOfSsp != -1) {
cacheKey = cacheKey.substring(indexOfSsp + 3);
}
return cacheKey.replaceAll("[^\\w\\d_]", String.valueOf(getSpecialCharacter()));
}
@Override
@Nullable
public String getMimeType(@NonNull Uri source) {
if (type == null || source.getQueryParameter(QUERY_PARAM_TYPE) != null) {
return context.getContentResolver().getType(source);
}
final Uri.Builder builder = source.buildUpon();
builder.appendQueryParameter(QUERY_PARAM_TYPE, type);
return context.getContentResolver().getType(builder.build());
}
@Override
public String getExtension(@Nullable String mimeType) {
if (mimeType == null) return null;
return MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType.toLowerCase(Locale.US));
}
@Override
public char getSpecialCharacter() {
return '_';
}
}
@StringDef({Type.IMAGE, Type.VIDEO, Type.JSON})
public @interface Type {
String IMAGE = "image";
String VIDEO = "video";
String JSON = "json";
}
}

View File

@ -293,6 +293,15 @@ public final class Utils implements Constants {
} }
} }
public static String sanitizeMimeType(@Nullable final String contentType) {
if (contentType == null) return null;
switch (contentType) {
case "image/jpg":
return "image/jpeg";
}
return contentType;
}
public static class NoAccountException extends Exception { public static class NoAccountException extends Exception {
String accountHost; String accountHost;

View File

@ -36,6 +36,7 @@ import org.mariotaku.twidere.util.JsonSerializer;
import org.mariotaku.twidere.util.MicroBlogAPIFactory; import org.mariotaku.twidere.util.MicroBlogAPIFactory;
import org.mariotaku.twidere.util.SharedPreferencesWrapper; import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.UserAgentUtils; import org.mariotaku.twidere.util.UserAgentUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor; import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor;
import org.mariotaku.twidere.util.net.NoIntercept; import org.mariotaku.twidere.util.net.NoIntercept;
@ -176,7 +177,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
} }
final Body body = resp.getBody(); final Body body = resp.getBody();
final CacheMetadata metadata = new CacheMetadata(); final CacheMetadata metadata = new CacheMetadata();
metadata.setContentType(body.contentType().getContentType()); metadata.setContentType(Utils.sanitizeMimeType(body.contentType().getContentType()));
return new TwidereDownloadResult(body, metadata); return new TwidereDownloadResult(body, metadata);
} }

View File

@ -45,19 +45,19 @@ public class UILFileCache implements FileCache {
} }
}); });
if (extra != null) { if (extra != null) {
cache.save(CacheProvider.getExtraKey(key), new ByteArrayInputStream(extra), null); cache.save(CacheProvider.Companion.getExtraKey(key), new ByteArrayInputStream(extra), null);
} }
} }
@NonNull @NonNull
@Override @Override
public Uri toUri(@NonNull final String key) { public Uri toUri(@NonNull final String key) {
return CacheProvider.getCacheUri(key, null); return CacheProvider.Companion.getCacheUri(key, null);
} }
@NonNull @NonNull
@Override @Override
public String fromUri(@NonNull final Uri uri) { public String fromUri(@NonNull final Uri uri) {
return CacheProvider.getCacheKey(uri); return CacheProvider.Companion.getCacheKey(uri);
} }
} }

View File

@ -48,6 +48,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.* import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper
import org.mariotaku.twidere.activity.iface.IExtendedActivity import org.mariotaku.twidere.activity.iface.IExtendedActivity
import org.mariotaku.twidere.annotation.CacheFileType
import org.mariotaku.twidere.fragment.* import org.mariotaku.twidere.fragment.*
import org.mariotaku.twidere.fragment.iface.IBaseFragment import org.mariotaku.twidere.fragment.iface.IBaseFragment
import org.mariotaku.twidere.model.ParcelableMedia import org.mariotaku.twidere.model.ParcelableMedia
@ -428,9 +429,9 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
val destination = ShareProvider.getFilesDir(this) ?: return val destination = ShareProvider.getFilesDir(this) ?: return
val type: String val type: String
when (f) { when (f) {
is VideoPageFragment -> type = CacheProvider.Type.VIDEO is VideoPageFragment -> type = CacheFileType.VIDEO
is ImagePageFragment -> type = CacheProvider.Type.IMAGE is ImagePageFragment -> type = CacheFileType.IMAGE
is GifPageFragment -> type = CacheProvider.Type.IMAGE is GifPageFragment -> type = CacheFileType.IMAGE
else -> throw UnsupportedOperationException("Unsupported fragment $f") else -> throw UnsupportedOperationException("Unsupported fragment $f")
} }
val task = object : SaveFileTask(this@MediaViewerActivity, cacheUri, destination, val task = object : SaveFileTask(this@MediaViewerActivity, cacheUri, destination,
@ -488,9 +489,9 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
val f = adapter.instantiateItem(viewPager, saveToStoragePosition) as? CacheDownloadMediaViewerFragment ?: return val f = adapter.instantiateItem(viewPager, saveToStoragePosition) as? CacheDownloadMediaViewerFragment ?: return
val cacheUri = f.downloadResult?.cacheUri ?: return val cacheUri = f.downloadResult?.cacheUri ?: return
val task: SaveFileTask = when (f) { val task: SaveFileTask = when (f) {
is ImagePageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE) is ImagePageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.IMAGE)
is VideoPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.VIDEO) is VideoPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.VIDEO)
is GifPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE) is GifPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.IMAGE)
else -> throw UnsupportedOperationException() else -> throw UnsupportedOperationException()
} }
AsyncTaskUtils.executeTask(task) AsyncTaskUtils.executeTask(task)

View File

@ -93,16 +93,15 @@ class WebLinkHandlerActivity : Activity() {
return Pair(Intent(Intent.ACTION_VIEW, builder.build()), true) return Pair(Intent(Intent.ACTION_VIEW, builder.build()), true)
} }
else -> { else -> {
if (!ArrayUtils.contains(FANFOU_RESERVED_PATHS, pathSegments[0])) { if (FANFOU_RESERVED_PATHS.contains(pathSegments[0])) return Pair(null, false)
if (pathSegments.size == 1) { if (pathSegments.size == 1) {
val builder = Uri.Builder() val builder = Uri.Builder()
builder.scheme(SCHEME_TWIDERE) builder.scheme(SCHEME_TWIDERE)
builder.authority(AUTHORITY_USER) builder.authority(AUTHORITY_USER)
builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM) builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_HOST, USER_TYPE_FANFOU_COM)
val userKey = UserKey(pathSegments[0], USER_TYPE_FANFOU_COM) val userKey = UserKey(pathSegments[0], USER_TYPE_FANFOU_COM)
builder.appendQueryParameter(QUERY_PARAM_USER_KEY, userKey.toString()) builder.appendQueryParameter(QUERY_PARAM_USER_KEY, userKey.toString())
return Pair(Intent(Intent.ACTION_VIEW, builder.build()), true) return Pair(Intent(Intent.ACTION_VIEW, builder.build()), true)
}
} }
return Pair(null, false) return Pair(null, false)
} }

View File

@ -26,6 +26,7 @@ import android.support.v4.content.AsyncTaskLoader
import org.mariotaku.ktextension.set import org.mariotaku.ktextension.set
import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.ErrorInfo import org.mariotaku.microblog.library.twitter.model.ErrorInfo
import org.mariotaku.restfu.http.RestHttpClient
import org.mariotaku.twidere.constant.IntentConstants import org.mariotaku.twidere.constant.IntentConstants
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT
import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.ParcelableStatus
@ -51,7 +52,9 @@ class ParcelableStatusLoader(
) : AsyncTaskLoader<SingleResponse<ParcelableStatus>>(context) { ) : AsyncTaskLoader<SingleResponse<ParcelableStatus>>(context) {
@Inject @Inject
lateinit var userColorNameManager: UserColorNameManager internal lateinit var userColorNameManager: UserColorNameManager
@Inject
internal lateinit var restHttpClient: RestHttpClient
init { init {
GeneralComponentHelper.build(context).inject(this) GeneralComponentHelper.build(context).inject(this)
@ -81,8 +84,7 @@ class ParcelableStatusLoader(
if (e.errorCode == ErrorInfo.STATUS_NOT_FOUND) { if (e.errorCode == ErrorInfo.STATUS_NOT_FOUND) {
// Delete all deleted status // Delete all deleted status
val cr = context.contentResolver val cr = context.contentResolver
DataStoreUtils.deleteStatus(cr, accountKey, DataStoreUtils.deleteStatus(cr, accountKey, statusId, null)
statusId, null)
DataStoreUtils.deleteActivityStatus(cr, accountKey, statusId, null) DataStoreUtils.deleteActivityStatus(cr, accountKey, statusId, null)
} }
return SingleResponse(e) return SingleResponse(e)
@ -94,4 +96,5 @@ class ParcelableStatusLoader(
forceLoad() forceLoad()
} }
} }

View File

@ -0,0 +1,194 @@
package org.mariotaku.twidere.provider
import android.content.ContentProvider
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.webkit.MimeTypeMap
import com.nostra13.universalimageloader.cache.disc.DiskCache
import okio.ByteString
import org.mariotaku.commons.logansquare.LoganSquareMapperFinder
import org.mariotaku.restfu.RestFuUtils
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.annotation.CacheFileType
import org.mariotaku.twidere.model.CacheMetadata
import org.mariotaku.twidere.task.SaveFileTask
import org.mariotaku.twidere.util.BitmapUtils
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
import javax.inject.Inject
/**
* Created by mariotaku on 16/1/1.
*/
class CacheProvider : ContentProvider() {
@Inject
internal lateinit var simpleDiskCache: DiskCache
override fun onCreate(): Boolean {
GeneralComponentHelper.build(context).inject(this)
return true
}
override fun query(uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
val metadata = getMetadata(uri)
if (metadata != null) {
return metadata.contentType
}
val type = uri.getQueryParameter(TwidereConstants.QUERY_PARAM_TYPE)
when (type) {
CacheFileType.IMAGE -> {
val file = simpleDiskCache.get(getCacheKey(uri)) ?: return null
return BitmapUtils.getImageMimeType(file)
}
CacheFileType.VIDEO -> {
return "video/mp4"
}
CacheFileType.JSON -> {
return "application/json"
}
}
return null
}
fun getMetadata(uri: Uri): CacheMetadata? {
val file = simpleDiskCache.get(getMetadataKey(uri)) ?: return null
var `is`: FileInputStream? = null
try {
val mapper = LoganSquareMapperFinder.mapperFor(CacheMetadata::class.java)
`is` = FileInputStream(file)
return mapper.parse(`is`)
} catch (e: IOException) {
return null
} finally {
RestFuUtils.closeSilently(`is`)
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
return null
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}
@Throws(FileNotFoundException::class)
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
try {
val file = simpleDiskCache.get(getCacheKey(uri)) ?: throw FileNotFoundException()
val modeBits = modeToMode(mode)
if (modeBits != ParcelFileDescriptor.MODE_READ_ONLY)
throw IllegalArgumentException("Cache can't be opened for write")
return ParcelFileDescriptor.open(file, modeBits)
} catch (e: IOException) {
throw FileNotFoundException()
}
}
class CacheFileTypeCallback(private val context: Context, @CacheFileType private val type: String?) : SaveFileTask.FileInfoCallback {
override fun getFilename(source: Uri): String {
var cacheKey = getCacheKey(source)
val indexOfSsp = cacheKey.indexOf("://")
if (indexOfSsp != -1) {
cacheKey = cacheKey.substring(indexOfSsp + 3)
}
return cacheKey.replace("[^\\w\\d_]".toRegex(), specialCharacter.toString())
}
override fun getMimeType(source: Uri): String? {
if (type == null || source.getQueryParameter(TwidereConstants.QUERY_PARAM_TYPE) != null) {
return context.contentResolver.getType(source)
}
val builder = source.buildUpon()
builder.appendQueryParameter(TwidereConstants.QUERY_PARAM_TYPE, type)
return context.contentResolver.getType(builder.build())
}
override fun getExtension(mimeType: String?): String? {
val typeLowered = mimeType?.toLowerCase(Locale.US) ?: return null
return when (typeLowered) {
// Hack for fanfou image type
"image/jpg" -> "jpg"
else -> MimeTypeMap.getSingleton().getExtensionFromMimeType(typeLowered)
}
}
override val specialCharacter: Char
get() = '_'
}
companion object {
fun getCacheUri(key: String, @CacheFileType type: String?): Uri {
val builder = Uri.Builder()
builder.scheme(ContentResolver.SCHEME_CONTENT)
builder.authority(TwidereConstants.AUTHORITY_TWIDERE_CACHE)
builder.appendPath(ByteString.encodeUtf8(key).base64Url())
if (type != null) {
builder.appendQueryParameter(TwidereConstants.QUERY_PARAM_TYPE, type)
}
return builder.build()
}
fun getCacheKey(uri: Uri): String {
if (ContentResolver.SCHEME_CONTENT != uri.scheme)
throw IllegalArgumentException(uri.toString())
if (TwidereConstants.AUTHORITY_TWIDERE_CACHE != uri.authority)
throw IllegalArgumentException(uri.toString())
return ByteString.decodeBase64(uri.lastPathSegment).utf8()
}
fun getMetadataKey(uri: Uri): String {
if (ContentResolver.SCHEME_CONTENT != uri.scheme)
throw IllegalArgumentException(uri.toString())
if (TwidereConstants.AUTHORITY_TWIDERE_CACHE != uri.authority)
throw IllegalArgumentException(uri.toString())
return getExtraKey(ByteString.decodeBase64(uri.lastPathSegment).utf8())
}
fun getExtraKey(key: String): String {
return key + ".extra"
}
/**
* Copied from ContentResolver.java
*/
private fun modeToMode(mode: String): Int {
val modeBits: Int
if ("r" == mode) {
modeBits = ParcelFileDescriptor.MODE_READ_ONLY
} else if ("w" == mode || "wt" == mode) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_TRUNCATE
} else if ("wa" == mode) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_APPEND
} else if ("rw" == mode) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE
} else if ("rwt" == mode) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_TRUNCATE
} else {
throw IllegalArgumentException("Invalid mode: " + mode)
}
return modeBits
}
}
}

View File

@ -33,8 +33,12 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
abstract class SaveFileTask(context: Context, private val source: Uri, abstract class SaveFileTask(
private val destination: File, private val getMimeType: SaveFileTask.FileInfoCallback) : AsyncTask<Any, Any, SaveFileTask.SaveFileResult>() { context: Context,
private val source: Uri,
private val destination: File,
private val getMimeType: SaveFileTask.FileInfoCallback
) : AsyncTask<Any, Any, SaveFileTask.SaveFileResult>() {
private val contextRef: WeakReference<Context> private val contextRef: WeakReference<Context>
@ -44,7 +48,7 @@ abstract class SaveFileTask(context: Context, private val source: Uri,
override fun doInBackground(vararg args: Any): SaveFileResult? { override fun doInBackground(vararg args: Any): SaveFileResult? {
val context = contextRef.get() ?: return null val context = contextRef.get() ?: return null
return saveFile(context, source, getMimeType, destination) return saveFile(context, source, getMimeType, destination, requiresValidExtension)
} }
override fun onCancelled() { override fun onCancelled() {
@ -72,6 +76,7 @@ abstract class SaveFileTask(context: Context, private val source: Uri,
protected abstract fun dismissProgress() protected abstract fun dismissProgress()
open val requiresValidExtension: Boolean = false
protected val context: Context? protected val context: Context?
get() = contextRef.get() get() = contextRef.get()
@ -102,7 +107,7 @@ abstract class SaveFileTask(context: Context, private val source: Uri,
fun saveFile(context: Context, source: Uri, fun saveFile(context: Context, source: Uri,
fileInfoCallback: FileInfoCallback, fileInfoCallback: FileInfoCallback,
destinationDir: File): SaveFileResult? { destinationDir: File, requiresValidExtension: Boolean): SaveFileResult? {
val cr = context.contentResolver val cr = context.contentResolver
var ioSrc: Source? = null var ioSrc: Source? = null
var sink: BufferedSink? = null var sink: BufferedSink? = null
@ -114,6 +119,9 @@ abstract class SaveFileTask(context: Context, private val source: Uri,
} }
val mimeType = fileInfoCallback.getMimeType(source) ?: return null val mimeType = fileInfoCallback.getMimeType(source) ?: return null
val extension = fileInfoCallback.getExtension(mimeType) val extension = fileInfoCallback.getExtension(mimeType)
if (requiresValidExtension && extension == null) {
return null
}
if (!destinationDir.isDirectory && !destinationDir.mkdirs()) return null if (!destinationDir.isDirectory && !destinationDir.mkdirs()) return null
var nameToSave = getFileNameWithExtension(name, extension, var nameToSave = getFileNameWithExtension(name, extension,
fileInfoCallback.specialCharacter, null) fileInfoCallback.specialCharacter, null)

View File

@ -25,6 +25,7 @@ import android.net.Uri
import android.os.Environment import android.os.Environment
import android.widget.Toast import android.widget.Toast
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.annotation.CacheFileType
import org.mariotaku.twidere.provider.CacheProvider import org.mariotaku.twidere.provider.CacheProvider
import java.io.File import java.io.File
@ -52,14 +53,13 @@ class SaveMediaToGalleryTask(
companion object { companion object {
fun create(activity: Activity, source: Uri, fun create(activity: Activity, source: Uri, @CacheFileType type: String): SaveFileTask {
@CacheProvider.Type type: String): SaveFileTask {
val pubDir: File val pubDir: File
when (type) { when (type) {
CacheProvider.Type.VIDEO -> { CacheFileType.VIDEO -> {
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
} }
CacheProvider.Type.IMAGE -> { CacheFileType.IMAGE -> {
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
} }
else -> { else -> {

View File

@ -42,6 +42,7 @@ import org.mariotaku.twidere.constant.newDocumentApiKey
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.ParcelableMediaUtils import org.mariotaku.twidere.model.util.ParcelableMediaUtils
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener
import org.mariotaku.twidere.util.TwidereLinkify.USER_TYPE_FANFOU_COM
import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor
open class OnLinkClickHandler( open class OnLinkClickHandler(
@ -72,16 +73,16 @@ open class OnLinkClickHandler(
return true return true
} }
TwidereLinkify.LINK_TYPE_LINK_IN_TEXT -> { TwidereLinkify.LINK_TYPE_LINK_IN_TEXT -> {
if (isMedia(link, extraId)) { if (accountKey != null && isMedia(link, extraId)) {
openMedia(accountKey!!, extraId, sensitive, link, start, end) openMedia(accountKey, extraId, sensitive, link, start, end)
} else { } else {
openLink(link) openLink(link)
} }
return true return true
} }
TwidereLinkify.LINK_TYPE_ENTITY_URL -> { TwidereLinkify.LINK_TYPE_ENTITY_URL -> {
if (isMedia(link, extraId)) { if (accountKey != null && isMedia(link, extraId)) {
openMedia(accountKey!!, extraId, sensitive, link, start, end) openMedia(accountKey, extraId, sensitive, link, start, end)
} else { } else {
val authority = UriUtils.getAuthority(link) val authority = UriUtils.getAuthority(link)
if (authority == null) { if (authority == null) {
@ -89,36 +90,12 @@ open class OnLinkClickHandler(
return true return true
} }
when (authority) { when (authority) {
"fanfou.com" -> { "fanfou.com" -> if (accountKey != null && handleFanfouLink(link, orig, accountKey)) {
if (orig != null) { return true
// Process special case for fanfou
val ch = orig[0]
// Extend selection
val length = orig.length
if (TwidereLinkify.isAtSymbol(ch)) {
var id = UriUtils.getPath(link)
if (id != null) {
val idxOfSlash = id.indexOf('/')
if (idxOfSlash == 0) {
id = id.substring(1)
}
val screenName = orig.substring(1, length)
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(id),
screenName, preferences[newDocumentApiKey], Referral.USER_MENTION,
null)
return true
}
} else if (TwidereLinkify.isHashSymbol(ch) && TwidereLinkify.isHashSymbol(orig[length - 1])) {
IntentUtils.openSearch(context, accountKey, orig.substring(1, length - 1))
return true
}
}
} }
else -> { else -> if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) {
if (IntentUtils.isWebLinkHandled(context, Uri.parse(link))) { openTwitterLink(link, accountKey!!)
openTwitterLink(link, accountKey!!) return true
return true
}
} }
} }
openLink(link) openLink(link)
@ -203,4 +180,36 @@ open class OnLinkClickHandler(
} }
} }
private fun handleFanfouLink(link: String, orig: String?, accountKey: UserKey): Boolean {
if (orig == null) return false
// Process special case for fanfou
val ch = orig[0]
// Extend selection
val length = orig.length
if (TwidereLinkify.isAtSymbol(ch)) {
var id = UriUtils.getPath(link)
if (id != null) {
val idxOfSlash = id.indexOf('/')
if (idxOfSlash == 0) {
id = id.substring(1)
}
val screenName = orig.substring(1, length)
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(id),
screenName, preferences[newDocumentApiKey], Referral.USER_MENTION,
null)
return true
}
} else if (TwidereLinkify.isHashSymbol(ch) && TwidereLinkify.isHashSymbol(orig[length - 1])) {
IntentUtils.openSearch(context, accountKey, orig.substring(1, length - 1))
return true
}
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
intent.setClass(context, WebLinkHandlerActivity::class.java)
if (accountKey.host == USER_TYPE_FANFOU_COM) {
intent.putExtra(EXTRA_ACCOUNT_KEY, accountKey)
}
context.startActivity(intent)
return true
}
} }

View File

@ -42,9 +42,6 @@ class NameView(context: Context, attrs: AttributeSet? = null) : AppCompatTextVie
var nameFirst: Boolean = false var nameFirst: Boolean = false
var twoLine: Boolean = false var twoLine: Boolean = false
get() {
return maxLines >= 2
}
set(value) { set(value) {
field = value field = value
if (value) { if (value) {

View File

@ -287,7 +287,7 @@
<string name="cards">Cards</string> <string name="cards">Cards</string>
<string name="clear_cache">Clear cache</string> <string name="clear_cache">Clear cache</string>
<string name="clear_cache_summary">Clear stored profile image cache.</string> <string name="clear_cache_summary">Clear stored media cache.</string>
<string name="clear_databases">Clear databases</string> <string name="clear_databases">Clear databases</string>
<string name="clear_databases_summary">Clear all tweets, profiles, messages. Your account credentials won\'t be lost.</string> <string name="clear_databases_summary">Clear all tweets, profiles, messages. Your account credentials won\'t be lost.</string>
<string name="clear_search_history">Clear search history</string> <string name="clear_search_history">Clear search history</string>