diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java index 17e012aa1..059ea5013 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java @@ -14,6 +14,7 @@ * see . */ package fr.gouv.etalab.mastodon.activities; +import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.content.BroadcastReceiver; @@ -22,6 +23,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.database.sqlite.SQLiteDatabase; import android.graphics.PorterDuff; import android.net.Uri; @@ -32,6 +34,7 @@ import android.support.annotation.NonNull; import android.support.design.widget.AppBarLayout; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.TabLayout; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; @@ -94,6 +97,7 @@ import fr.gouv.etalab.mastodon.interfaces.OnRetrieveInstanceInterface; import fr.gouv.etalab.mastodon.interfaces.OnRetrieveMetaDataInterface; import fr.gouv.etalab.mastodon.interfaces.OnRetrieveRemoteAccountInterface; import fr.gouv.etalab.mastodon.interfaces.OnUpdateAccountInfoInterface; +import fr.gouv.etalab.mastodon.services.BackupStatusService; import fr.gouv.etalab.mastodon.services.LiveNotificationService; import fr.gouv.etalab.mastodon.sqlite.Sqlite; import fr.gouv.etalab.mastodon.asynctasks.RetrieveAccountsAsyncTask; @@ -105,6 +109,7 @@ import fr.gouv.etalab.mastodon.sqlite.AccountDAO; import static fr.gouv.etalab.mastodon.helper.Helper.ADD_USER_INTENT; import static fr.gouv.etalab.mastodon.helper.Helper.CHANGE_THEME_INTENT; import static fr.gouv.etalab.mastodon.helper.Helper.CHANGE_USER_INTENT; +import static fr.gouv.etalab.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE; import static fr.gouv.etalab.mastodon.helper.Helper.HOME_TIMELINE_INTENT; import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_ACTION; import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_TARGETED_ACCOUNT; @@ -735,6 +740,21 @@ public abstract class BaseMainActivity extends BaseActivity .setIcon(android.R.drawable.ic_dialog_alert) .show(); return true; + case R.id.action_export: + if(Build.VERSION.SDK_INT >= 23 ){ + if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) { + ActivityCompat.requestPermissions(BaseMainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE); + } else { + Intent backupIntent = new Intent(BaseMainActivity.this, BackupStatusService.class); + backupIntent.putExtra("userId", userId); + startService(backupIntent); + } + }else{ + Intent backupIntent = new Intent(BaseMainActivity.this, BackupStatusService.class); + backupIntent.putExtra("userId", userId); + startService(backupIntent); + } + return true; default: return true; } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java index 8039876c3..321413ed8 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java @@ -350,8 +350,10 @@ public class API { apiResponse.setMax_id(httpsConnection.getMax_id()); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); + e.printStackTrace(); }catch (Exception e) { setDefaultError(e); + e.printStackTrace(); } apiResponse.setStatuses(statuses); return apiResponse; diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java index 9a46af0bd..91c8fbfea 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java @@ -461,6 +461,24 @@ public class Helper { return dateFormat.format(date); } + /** + * Convert a date in String -> format yyyy-MM-dd HH:mm:ss + * @param context Context + * @param date Date + * @return String + */ + public static String dateFileToString(Context context, Date date) { + Locale userLocale; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + userLocale = context.getResources().getConfiguration().getLocales().get(0); + } else { + //noinspection deprecation + userLocale = context.getResources().getConfiguration().locale; + } + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss",userLocale); + return dateFormat.format(date); + } + /** * Convert String date from db to Date Object * @param stringDate date to convert diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/services/BackupStatusService.java b/app/src/main/java/fr/gouv/etalab/mastodon/services/BackupStatusService.java index eab23a81f..2cfcefc6e 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/services/BackupStatusService.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/services/BackupStatusService.java @@ -22,15 +22,17 @@ import android.database.sqlite.SQLiteDatabase; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; -import android.os.Bundle; import android.os.Environment; +import android.os.Handler; +import android.os.Looper; import android.support.annotation.Nullable; -import android.support.v4.content.LocalBroadcastManager; import android.text.Html; -import org.json.JSONObject; +import android.widget.Toast; + import java.io.File; -import java.io.FileWriter; -import java.io.IOException; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -56,6 +58,7 @@ import static fr.gouv.etalab.mastodon.helper.Helper.notify_user; public class BackupStatusService extends IntentService { + private static int instanceRunning = 0; /** * Creates an IntentService. Invoked by your subclass's constructor. * @@ -79,115 +82,124 @@ public class BackupStatusService extends IntentService { @Override protected void onHandleIntent(@Nullable Intent intent) { + if( instanceRunning == 0 ){ + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Toast.makeText(getApplicationContext(), R.string.data_export_start, Toast.LENGTH_LONG).show(); + } + }); + }else { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Toast.makeText(getApplicationContext(), R.string.data_export_running, Toast.LENGTH_LONG).show(); + } + }); + return; + } + instanceRunning++; + String message; SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - if( userId == null) - return; SQLiteDatabase db = Sqlite.getInstance(BackupStatusService.this, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); Account account = new AccountDAO(getApplicationContext(), db).getAccountByID(userId); - API api = new API(getApplicationContext(), account.getId(), account.getToken()); - - String max_id = "0"; - int statusToBackUp = account.getStatuses_count(); - List backupStatus = new ArrayList<>(); - while (max_id != null){ - APIResponse apiResponse = api.getStatus(userId, null); - max_id = apiResponse.getMax_id(); - List statuses = apiResponse.getStatuses(); - if (statuses.size() > 0) - backupStatus.addAll(statuses); - } - String message; - String fileName = account.getAcct()+"@"+account.getInstance()+ Helper.dateToString(getApplicationContext(), new Date())+".csv"; - String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - String fullPath = filePath+"/"+fileName; + API api = new API(getApplicationContext(), account.getInstance(), account.getToken()); try { - FileWriter fw = new FileWriter(fullPath); - fw.append("id"); - fw.append(','); - fw.append("uri"); - fw.append(','); - fw.append("url"); - fw.append(','); - fw.append("account"); - fw.append(','); - fw.append("in_reply_to_id"); - fw.append(','); - fw.append("in_reply_to_account_id"); - fw.append(','); - fw.append("content"); - fw.append(','); - fw.append("created_at"); - fw.append(','); - fw.append("reblogs_count"); - fw.append(','); - fw.append("favourites_count"); - fw.append(','); - fw.append("sensitive"); - fw.append(','); - fw.append("spoiler_text"); - fw.append(','); - fw.append("visibility"); - fw.append(','); - fw.append("media_attachments"); - fw.append('\n'); + String fullPath; + Intent intentOpen; + String max_id = null; + int statusToBackUp = account.getStatuses_count(); + List backupStatus = new ArrayList<>(); + do { + APIResponse apiResponse = api.getStatus(userId, max_id); + max_id = apiResponse.getMax_id(); + List statuses = apiResponse.getStatuses(); + if (statuses.size() > 0) + backupStatus.addAll(statuses); + }while (max_id != null); + + String fileName = account.getAcct()+"@"+account.getInstance()+ Helper.dateFileToString(getApplicationContext(), new Date())+".csv"; + String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); + fullPath = filePath+"/"+fileName; + PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(fullPath)), "UTF-8")); + StringBuilder builder = new StringBuilder(); + builder.append("id").append(','); + builder.append("uri").append(','); + builder.append("url").append(','); + builder.append("account").append(','); + builder.append("in_reply_to_id").append(','); + builder.append("in_reply_to_account_id").append(','); + builder.append("content").append(','); + builder.append("created_at").append(','); + builder.append("reblogs_count").append(','); + builder.append("favourites_count").append(','); + builder.append("sensitive").append(','); + builder.append("spoiler_text").append(','); + builder.append("visibility").append(','); + builder.append("media_attachments"); + builder.append('\n'); for( Status status: backupStatus){ - fw.append(status.getId()); - fw.append(','); - fw.append(status.getUri()); - fw.append(','); - fw.append(status.getUrl()); - fw.append(','); - fw.append(status.getAccount().getAcct()); - fw.append(','); - fw.append(status.getIn_reply_to_id()); - fw.append(','); - fw.append(status.getIn_reply_to_account_id()); - fw.append(','); + //excludes reblog + if( status.getReblog() != null){ + statusToBackUp = statusToBackUp - 1; + continue; + } + builder.append("\"").append(status.getId()).append("\"").append(','); + builder.append("\"").append(status.getUri()).append("\"").append(','); + builder.append("\"").append(status.getUrl()).append("\"").append(','); + builder.append("\"").append(status.getAccount().getAcct()).append("\"").append(','); + builder.append("\"").append(status.getIn_reply_to_id()).append("\"").append(','); + builder.append("\"").append(status.getIn_reply_to_account_id()).append("\"").append(','); String content; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - content = Html.fromHtml(status.getContentTranslated(), Html.FROM_HTML_MODE_LEGACY).toString(); + content = Html.fromHtml(status.getContent(), Html.FROM_HTML_MODE_LEGACY).toString(); else //noinspection deprecation - content = Html.fromHtml(status.getContentTranslated()).toString(); - fw.append(content); - fw.append(','); - fw.append(Helper.shortDateTime(getApplicationContext(), status.getCreated_at())); - fw.append(','); - fw.append(String.valueOf(status.getReblogs_count())); - fw.append(','); - fw.append(String.valueOf(status.getFavourites_count())); - fw.append(','); - fw.append(String.valueOf(status.isSensitive())); - fw.append(','); - fw.append(status.getSpoiler_text() !=null?status.getSpoiler_text():""); - fw.append(','); - fw.append(status.getVisibility()); - fw.append(','); + content = Html.fromHtml(status.getContent()).toString(); + builder.append("\"").append(content.replace("\"", "'").replace("\n"," ")).append("\"").append(','); + builder.append("\"").append(Helper.shortDateTime(getApplicationContext(), status.getCreated_at())).append("\"").append(','); + builder.append("\"").append(String.valueOf(status.getReblogs_count())).append("\"").append(','); + builder.append("\"").append(String.valueOf(status.getFavourites_count())).append("\"").append(','); + builder.append("\"").append(String.valueOf(status.isSensitive())).append("\"").append(','); + builder.append("\"").append(status.getSpoiler_text() !=null?status.getSpoiler_text():"").append("\"").append(','); + builder.append("\"").append(status.getVisibility()).append("\"").append(','); if( status.getMedia_attachments() != null && status.getMedia_attachments().size() > 0){ + builder.append("\""); for(Attachment attachment: status.getMedia_attachments()){ - fw.append(attachment.getRemote_url()).append("\n"); + builder.append(attachment.getUrl()).append(" "); } + builder.append("\""); }else { - fw.append(""); + builder.append("\"\""); } - fw.append('\n'); + builder.append('\n'); } - fw.flush(); - fw.close(); - message = getString(R.string.data_export_success, account.getAcct()); - } catch (IOException e) { + pw.write(builder.toString()); + pw.close(); + message = getString(R.string.data_export_success, String.valueOf(statusToBackUp), String.valueOf(backupStatus.size())); + intentOpen = new Intent(); + intentOpen.setAction(android.content.Intent.ACTION_VIEW); + Uri uri = Uri.parse("file://" + fullPath); + intentOpen.setDataAndType(uri, "text/csv"); + long notif_id = Long.parseLong(account.getId()); + int notificationId = ((notif_id + 3) > 2147483647) ? (int) (2147483647 - notif_id - 3) : (int) (notif_id + 3); + String title = getString(R.string.data_export_toots, account.getAcct()); + notify_user(getApplicationContext(), intentOpen, notificationId, BitmapFactory.decodeResource(getResources(), + R.drawable.mastodonlogo), title, message); + } catch (Exception e) { e.printStackTrace(); message = getString(R.string.data_export_error, account.getAcct()); + final String finalMessage = message; + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Toast.makeText(getApplicationContext(), finalMessage, Toast.LENGTH_LONG).show(); + } + }); } - long notif_id = Long.parseLong(account.getId()); - int notificationId = ((notif_id + 3) > 2147483647) ? (int) (2147483647 - notif_id - 3) : (int) (notif_id + 3); - Intent intentOpen = new Intent(); - intentOpen.setAction(android.content.Intent.ACTION_VIEW); - Uri uri = Uri.parse("file://" + fullPath); - intentOpen.setDataAndType(uri, "text/csv"); - notify_user(getApplicationContext(), intentOpen, notificationId, BitmapFactory.decodeResource(getResources(), - R.drawable.mastodonlogo), getString(R.string.data_export), message); + instanceRunning--; + } diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index dec1cfe65..fdf19203e 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -21,6 +21,10 @@ android:id="@+id/action_cache" android:title="@string/action_cache" app:showAsAction="never" /> + Media has been loaded. Click here to display it. - Data export - Data have been exported for %1$s + This action can be quite long. You will be notified when it will be finished. + Still running, please wait… + Export statuses + Export statuses for %1$s + %1$s toots out of %2$s have been exported. Something went wrong when exporting data for %1$s \ No newline at end of file