diff --git a/Yeeemp/app/src/main/AndroidManifest.xml b/Yeeemp/app/src/main/AndroidManifest.xml index 9b0c1a5..c7243d7 100644 --- a/Yeeemp/app/src/main/AndroidManifest.xml +++ b/Yeeemp/app/src/main/AndroidManifest.xml @@ -1,7 +1,11 @@ - + + + 0) { + fos.write(buffer, 0, lengthRead); + fos.flush(); + } + } + + /* just copy internal db to external storage */ + public static void exportDatabase(Context context, File directory) throws Exception { + directory.mkdirs(); + + File internalFile = DBWrapper.getDBPath(context); + File externalFile = new File(directory, "export_" + formatTs(System.currentTimeMillis()) + ".db"); + FileInputStream fis = new FileInputStream(internalFile); + FileOutputStream fos = new FileOutputStream(externalFile); + + copyStream(fis, fos); + + fis.close(); + fos.close(); + } + + /* just copy internal db to external storage */ + public static void exportDatabase(Context context, Uri uri) throws Exception { + File internalFile = DBWrapper.getDBPath(context); + InputStream fis = new FileInputStream(internalFile); + OutputStream fos = context.getContentResolver().openOutputStream(uri); + + copyStream(fis, fos); + + fis.close(); + fos.close(); + } + + /* just copy external db to internal storage */ + public static void importDatabase(Context context, Uri uri) throws Exception { + File internalFile = DBWrapper.getDBPath(context); + InputStream fis = context.getContentResolver().openInputStream(uri); + OutputStream fos = new FileOutputStream(internalFile); + + copyStream(fis, fos); + + fis.close(); + fos.close(); + } + + public static void backupDatabase(Context context) throws IOException { + File internalFile = DBWrapper.getDBPath(context); + File backupFile = new File(context.getFilesDir(), "backup.db"); + + FileInputStream fis = new FileInputStream(internalFile); + FileOutputStream fos = new FileOutputStream(backupFile); + + copyStream(fis, fos); + + fis.close(); + fos.close(); + } + + public static void restoreDatabase(Context context) throws IOException { + File internalFile = DBWrapper.getDBPath(context); + File backupFile = new File(context.getFilesDir(), "backup.db"); + + FileInputStream fis = new FileInputStream(backupFile); + FileOutputStream fos = new FileOutputStream(internalFile); + + copyStream(fis, fos); + + fis.close(); + fos.close(); + } + + public static void deleteBackupDatabase(Context context) throws IOException { + File backupFile = new File(context.getFilesDir(), "backup.db"); + backupFile.delete(); + } +} diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Init.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Init.java index 87c7ea5..a44a35f 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Init.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Init.java @@ -25,4 +25,8 @@ public class Init { if (Wrapper.instance() == null) Wrapper.setInstance(new DBWrapper(context)); } + + public static void reinitDB(Context context) { + Wrapper.setInstance(new DBWrapper(context)); + } } \ No newline at end of file diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java index d26418e..b0115c0 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java @@ -16,29 +16,53 @@ package art.pegasko.yeeemp.ui.activity; +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import com.google.android.material.snackbar.Snackbar; +import androidx.activity.result.ActivityResultLauncher; import androidx.appcompat.app.AppCompatActivity; +import android.os.Environment; import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; +import android.widget.Toast; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.PermissionChecker; import androidx.navigation.ui.AppBarConfiguration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.io.File; +import java.security.Permission; + import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Wrapper; import art.pegasko.yeeemp.databinding.ActivityQueueListBinding; import art.pegasko.yeeemp.R; +import art.pegasko.yeeemp.impl.DBWrapper; +import art.pegasko.yeeemp.impl.DataUtils; import art.pegasko.yeeemp.impl.Init; public class QueueListActivity extends AppCompatActivity { public static final String TAG = QueueListActivity.class.getSimpleName(); + private static final int REQUEST_CODE_CREATE_FILE = 37; + private static final int REQUEST_CODE_OPEN_FILE = 19; + private AppBarConfiguration appBarConfiguration; private ActivityQueueListBinding binding; private RecyclerView queueList; @@ -60,6 +84,26 @@ public class QueueListActivity extends AppCompatActivity { setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); + /* Toolbar menu */ + binding.toolbar.inflateMenu(R.menu.queue_list_toolbar_menu); + binding.toolbar.setOnMenuItemClickListener((MenuItem item) -> { + if (item.getItemId() == R.id.queue_list_toolbar_menu_export) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_TITLE, "export_" + DataUtils.formatTs(System.currentTimeMillis()) + ".db"); + startActivityForResult(intent, REQUEST_CODE_CREATE_FILE); + + return true; + } else if (item.getItemId() == R.id.queue_list_toolbar_menu_import) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + startActivityForResult(intent, REQUEST_CODE_OPEN_FILE); + + return true; + } + return false; + }); + /* Queue list */ queueListAdapter = new QueueRecyclerViewAdapter(); queueList = findViewById(R.id.content_queue_list__list); @@ -86,6 +130,113 @@ public class QueueListActivity extends AppCompatActivity { updateList(); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent resultData) { + super.onActivityResult(requestCode, resultCode, resultData); + + if (requestCode == REQUEST_CODE_CREATE_FILE && resultCode == Activity.RESULT_OK) { + if (resultData != null) { + Uri uri = resultData.getData(); + Log.i(TAG, "Exporting file to " + uri.toString()); + + try { + DataUtils.exportDatabase(getApplicationContext(), uri); + Toast.makeText(QueueListActivity.this, "Exported database", Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + Log.wtf(TAG, e); + + new AlertDialog + .Builder(QueueListActivity.this) + .setTitle("Export failed") + .setMessage(e.getMessage()) + .setCancelable(true) + .setNegativeButton("OK", (DialogInterface dialog, int id) -> { dialog.cancel(); }) + .show(); + } + } + } else if (requestCode == REQUEST_CODE_OPEN_FILE && resultCode == Activity.RESULT_OK) { + if (resultData != null) { + final Uri uri = resultData.getData(); + + new AlertDialog + .Builder(QueueListActivity.this) + .setTitle("Confirm action") + .setMessage("Import file as database? This will overwrite local database with external file without checking") + .setCancelable(true) + .setPositiveButton("Yes", (DialogInterface dialog, int id) -> { + Log.i(TAG, "Importing file from " + uri.toString()); + + try { + Log.i(TAG, "Backup before restore"); + DataUtils.backupDatabase(getApplicationContext()); + + Log.i(TAG, "Importing database"); + DataUtils.importDatabase(getApplicationContext(), uri); + Toast.makeText(QueueListActivity.this, "Imported database", Toast.LENGTH_SHORT).show(); + + Log.i(TAG, "Reloading database"); + Init.reinitDB(getApplicationContext()); + updateList(); + + Log.i(TAG, "Delete backup"); + DataUtils.deleteBackupDatabase(getApplicationContext()); + } catch (Exception e) { + Log.e(TAG, "Import failed"); + Log.wtf(TAG, e); + + new AlertDialog + .Builder(QueueListActivity.this) + .setTitle("Import failed") + .setMessage(e.getMessage()) + .setCancelable(true) + .setNegativeButton("OK", (DialogInterface dialog2, int id2) -> { dialog2.cancel(); }) + .show(); + + try { + Log.i(TAG, "Trying to restore backup"); + DataUtils.restoreDatabase(getApplicationContext()); + + Log.i(TAG, "Reloading database"); + Init.reinitDB(getApplicationContext()); + updateList(); + + Log.i(TAG, "Delete backup"); + DataUtils.deleteBackupDatabase(getApplicationContext()); + + new AlertDialog + .Builder(QueueListActivity.this) + .setTitle("Info") + .setMessage("Restored backup") + .setCancelable(true) + .setNegativeButton("OK", (DialogInterface dialog2, int id2) -> { dialog2.cancel(); }) + .show(); + + } catch (Exception e2) { + Log.e(TAG, "Restore backup failed"); + Log.wtf(TAG, e2); + + new AlertDialog + .Builder(QueueListActivity.this) + .setTitle("Restore backup failed") + .setMessage(e2.getMessage()) + .setCancelable(true) + .setNegativeButton("OK", (DialogInterface dialog2, int id2) -> { dialog2.cancel(); }) + .show(); + } + } + }) + .setNegativeButton("Cancel", (DialogInterface dialog, int id) -> { dialog.cancel(); }) + .show(); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.queue_list_toolbar_menu, menu); + return true; + } + @Override protected void onResume() { super.onResume(); diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/Utils.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/Utils.java index 8d91f67..3663462 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/Utils.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/Utils.java @@ -49,4 +49,10 @@ public class Utils { return finalItems; } + + public static int positiveHashCode16(Object o) { + if (o == null) + return 0; + return Math.abs(o.hashCode()) & ((1 << 16) - 1); + } } diff --git a/Yeeemp/app/src/main/res/menu/queue_list_toolbar_menu.xml b/Yeeemp/app/src/main/res/menu/queue_list_toolbar_menu.xml new file mode 100644 index 0000000..261d8f1 --- /dev/null +++ b/Yeeemp/app/src/main/res/menu/queue_list_toolbar_menu.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file