2018-04-14 06:37:21 +10:00
|
|
|
|
package com.keylesspalace.tusky.util;
|
|
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint;
|
|
|
|
|
import android.content.ContentResolver;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.AsyncTask;
|
2018-12-18 01:25:35 +11:00
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
|
import androidx.core.content.FileProvider;
|
2018-04-14 06:37:21 +10:00
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
import android.webkit.MimeTypeMap;
|
|
|
|
|
|
|
|
|
|
import com.google.gson.Gson;
|
|
|
|
|
import com.google.gson.reflect.TypeToken;
|
|
|
|
|
import com.keylesspalace.tusky.BuildConfig;
|
|
|
|
|
import com.keylesspalace.tusky.db.TootDao;
|
|
|
|
|
import com.keylesspalace.tusky.db.TootEntity;
|
|
|
|
|
import com.keylesspalace.tusky.entity.Status;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Locale;
|
|
|
|
|
|
|
|
|
|
public final class SaveTootHelper {
|
|
|
|
|
|
|
|
|
|
private static final String TAG = "SaveTootHelper";
|
|
|
|
|
|
|
|
|
|
private TootDao tootDao;
|
|
|
|
|
private Context context;
|
2018-11-12 05:25:45 +11:00
|
|
|
|
private Gson gson = new Gson();
|
2018-04-14 06:37:21 +10:00
|
|
|
|
|
|
|
|
|
public SaveTootHelper(@NonNull TootDao tootDao, @NonNull Context context) {
|
|
|
|
|
this.tootDao = tootDao;
|
|
|
|
|
this.context = context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
|
|
|
public boolean saveToot(@NonNull String content,
|
|
|
|
|
@NonNull String contentWarning,
|
|
|
|
|
@Nullable String savedJsonUrls,
|
|
|
|
|
@NonNull List<String> mediaUris,
|
2018-11-12 05:25:45 +11:00
|
|
|
|
@NonNull List<String> mediaDescriptions,
|
2018-04-14 06:37:21 +10:00
|
|
|
|
int savedTootUid,
|
|
|
|
|
@Nullable String inReplyToId,
|
|
|
|
|
@Nullable String replyingStatusContent,
|
|
|
|
|
@Nullable String replyingStatusAuthorUsername,
|
|
|
|
|
@NonNull Status.Visibility statusVisibility) {
|
|
|
|
|
|
|
|
|
|
if (TextUtils.isEmpty(content) && mediaUris.isEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get any existing file's URIs.
|
|
|
|
|
ArrayList<String> existingUris = null;
|
|
|
|
|
if (!TextUtils.isEmpty(savedJsonUrls)) {
|
2018-11-12 05:25:45 +11:00
|
|
|
|
existingUris = gson.fromJson(savedJsonUrls,
|
2018-04-14 06:37:21 +10:00
|
|
|
|
new TypeToken<ArrayList<String>>() {
|
|
|
|
|
}.getType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String mediaUrlsSerialized = null;
|
2018-11-12 05:25:45 +11:00
|
|
|
|
String mediaDescriptionsSerialized = null;
|
|
|
|
|
|
2018-04-14 06:37:21 +10:00
|
|
|
|
if (!ListUtils.isEmpty(mediaUris)) {
|
|
|
|
|
List<String> savedList = saveMedia(mediaUris, existingUris);
|
|
|
|
|
if (!ListUtils.isEmpty(savedList)) {
|
2018-11-12 05:25:45 +11:00
|
|
|
|
mediaUrlsSerialized = gson.toJson(savedList);
|
2018-04-14 06:37:21 +10:00
|
|
|
|
if (!ListUtils.isEmpty(existingUris)) {
|
|
|
|
|
deleteMedia(setDifference(existingUris, savedList));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-11-12 05:25:45 +11:00
|
|
|
|
mediaDescriptionsSerialized = gson.toJson(mediaDescriptions);
|
2018-04-14 06:37:21 +10:00
|
|
|
|
} else if (!ListUtils.isEmpty(existingUris)) {
|
|
|
|
|
/* If there were URIs in the previous draft, but they've now been removed, those files
|
|
|
|
|
* can be deleted. */
|
|
|
|
|
deleteMedia(existingUris);
|
|
|
|
|
}
|
2018-11-12 05:25:45 +11:00
|
|
|
|
final TootEntity toot = new TootEntity(savedTootUid, content, mediaUrlsSerialized, mediaDescriptionsSerialized, contentWarning,
|
2018-04-14 06:37:21 +10:00
|
|
|
|
inReplyToId,
|
|
|
|
|
replyingStatusContent,
|
|
|
|
|
replyingStatusAuthorUsername,
|
|
|
|
|
statusVisibility);
|
|
|
|
|
|
|
|
|
|
new AsyncTask<Void, Void, Void>() {
|
|
|
|
|
@Override
|
|
|
|
|
protected Void doInBackground(Void... params) {
|
|
|
|
|
tootDao.insertOrReplace(toot);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}.execute();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void deleteDraft(int tootId) {
|
|
|
|
|
TootEntity item = tootDao.find(tootId);
|
|
|
|
|
if(item != null) {
|
|
|
|
|
deleteDraft(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void deleteDraft(@NonNull TootEntity item){
|
|
|
|
|
// Delete any media files associated with the status.
|
2018-11-12 05:25:45 +11:00
|
|
|
|
ArrayList<String> uris = gson.fromJson(item.getUrls(),
|
2018-04-14 06:37:21 +10:00
|
|
|
|
new TypeToken<ArrayList<String>>() {}.getType());
|
|
|
|
|
if (uris != null) {
|
|
|
|
|
for (String uriString : uris) {
|
|
|
|
|
Uri uri = Uri.parse(uriString);
|
|
|
|
|
if (context.getContentResolver().delete(uri, null, null) == 0) {
|
|
|
|
|
Log.e(TAG, String.format("Did not delete file %s.", uriString));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// update DB
|
|
|
|
|
tootDao.delete(item.getUid());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
|
private List<String> saveMedia(@NonNull List<String> mediaUris,
|
|
|
|
|
@Nullable List<String> existingUris) {
|
|
|
|
|
|
|
|
|
|
File directory = context.getExternalFilesDir("Tusky");
|
|
|
|
|
|
|
|
|
|
if (directory == null || !(directory.exists())) {
|
|
|
|
|
Log.e(TAG, "Error obtaining directory to save media.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ContentResolver contentResolver = context.getContentResolver();
|
|
|
|
|
ArrayList<File> filesSoFar = new ArrayList<>();
|
|
|
|
|
ArrayList<String> results = new ArrayList<>();
|
|
|
|
|
for (String mediaUri : mediaUris) {
|
|
|
|
|
/* If the media was already saved in a previous draft, there's no need to save another
|
|
|
|
|
* copy, just add the existing URI to the results. */
|
|
|
|
|
if (existingUris != null) {
|
|
|
|
|
int index = existingUris.indexOf(mediaUri);
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
results.add(mediaUri);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, save the media.
|
|
|
|
|
|
|
|
|
|
Uri uri = Uri.parse(mediaUri);
|
|
|
|
|
|
|
|
|
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
|
|
|
|
|
|
|
|
|
String mimeType = contentResolver.getType(uri);
|
|
|
|
|
MimeTypeMap map = MimeTypeMap.getSingleton();
|
|
|
|
|
String fileExtension = map.getExtensionFromMimeType(mimeType);
|
|
|
|
|
String filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension);
|
|
|
|
|
File file = new File(directory, filename);
|
|
|
|
|
filesSoFar.add(file);
|
|
|
|
|
boolean copied = IOUtils.copyToFile(contentResolver, uri, file);
|
|
|
|
|
if (!copied) {
|
|
|
|
|
/* If any media files were created in prior iterations, delete those before
|
|
|
|
|
* returning. */
|
|
|
|
|
for (File earlierFile : filesSoFar) {
|
|
|
|
|
boolean deleted = earlierFile.delete();
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
Log.i(TAG, "Could not delete the file " + earlierFile.toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Uri resultUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID+".fileprovider", file);
|
|
|
|
|
results.add(resultUri.toString());
|
|
|
|
|
}
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void deleteMedia(List<String> mediaUris) {
|
|
|
|
|
for (String uriString : mediaUris) {
|
|
|
|
|
Uri uri = Uri.parse(uriString);
|
|
|
|
|
if (context.getContentResolver().delete(uri, null, null) == 0) {
|
|
|
|
|
Log.e(TAG, String.format("Did not delete file %s.", uriString));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A∖B={x∈A|x∉B}
|
|
|
|
|
*
|
|
|
|
|
* @return all elements of set A that are not in set B.
|
|
|
|
|
*/
|
|
|
|
|
private static List<String> setDifference(List<String> a, List<String> b) {
|
|
|
|
|
List<String> c = new ArrayList<>();
|
|
|
|
|
for (String s : a) {
|
|
|
|
|
if (!b.contains(s)) {
|
|
|
|
|
c.add(s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|