You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Wonerujo/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java

171 lines
6.5 KiB

package com.m2049r.xmrwallet.util;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.preference.PreferenceManager;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.WalletManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import timber.log.Timber;
@RequiredArgsConstructor
public class LegacyStorageHelper {
final private File srcDir;
final private File dstDir;
static public void migrateWallets(Context context) {
try {
if (isStorageMigrated(context)) return;
if (!hasReadPermission(context)) {
// can't migrate - don't remember this, as the user may turn on permissions later
return;
}
final File oldRoot = getWalletRoot();
if (!oldRoot.exists()) {
// nothing to migrate, so don't try again
setStorageMigrated(context);
return;
}
final File newRoot = Helper.getWalletRoot(context);
(new LegacyStorageHelper(oldRoot, newRoot)).migrate();
setStorageMigrated(context); // done it once - don't try again
} catch (IllegalStateException ex) {
Timber.d(ex);
// nothing we can do here
}
}
public void migrate() {
String addressPrefix = WalletManager.getInstance().addressPrefix();
File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys"));
if (wallets == null) return;
for (File wallet : wallets) {
final String walletName = wallet.getName().substring(0, wallet.getName().length() - ".keys".length());
if (addressPrefix.indexOf(getAddress(walletName).charAt(0)) < 0) {
Timber.d("skipping %s", walletName);
continue;
}
try {
copy(walletName);
} catch (IOException ex) { // something failed - try to clean up
deleteDst(walletName);
}
}
}
// return "@" by default so we don't need to deal with null stuff
private String getAddress(String walletName) {
File addressFile = new File(srcDir, walletName + ".address.txt");
if (!addressFile.exists()) return "@";
try (BufferedReader addressReader = new BufferedReader(new FileReader(addressFile))) {
return addressReader.readLine();
} catch (IOException ex) {
Timber.d(ex.getLocalizedMessage());
}
return "@";
}
private void copy(String walletName) throws IOException {
final String dstName = getUniqueName(dstDir, walletName);
copyFile(new File(srcDir, walletName), new File(dstDir, dstName));
copyFile(new File(srcDir, walletName + ".keys"), new File(dstDir, dstName + ".keys"));
}
private void deleteDst(String walletName) {
// do our best, but if it fails, it fails
(new File(dstDir, walletName)).delete();
(new File(dstDir, walletName + ".keys")).delete();
}
private void copyFile(File src, File dst) throws IOException {
if (!src.exists()) return;
Timber.d("%s => %s", src.getAbsolutePath(), dst.getAbsolutePath());
try (FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
private static File getWalletRoot() {
if (!isExternalStorageWritable())
throw new IllegalStateException();
// wallet folder for legacy (pre-Q) installations
final String FLAVOR_SUFFIX =
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.DEBUG ? "-debug" : "");
final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
if (!dir.exists() || !dir.isDirectory())
throw new IllegalStateException();
return dir;
}
private static boolean hasReadPermission(Context context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
} else {
return true;
}
}
private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
private static String getUniqueName(File root, String name) {
if (!(new File(root, name + ".keys")).exists()) // <name> does not exist => it's ok to use
return name;
File[] wallets = root.listFiles(
(dir, filename) -> {
Matcher m = WALLET_PATTERN.matcher(filename);
if (m.find())
return m.group(1).equals(name);
else return false;
});
if (wallets.length == 0) return name + " (1)";
int maxIndex = 0;
for (File wallet : wallets) {
try {
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
if (!m.find())
throw new IllegalStateException("this must match as it did before");
final int index = Integer.parseInt(m.group(2));
if (index > maxIndex) maxIndex = index;
} catch (NumberFormatException ex) {
// this cannot happen & we can ignore it if it does
}
}
return name + " (" + (maxIndex + 1) + ")";
}
private static final String MIGRATED_KEY = "migrated_legacy_storage";
public static boolean isStorageMigrated(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false);
}
public static void setStorageMigrated(Context context) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply();
}
}