diff --git a/android/jni/com/mapswithme/platform/Platform.cpp b/android/jni/com/mapswithme/platform/Platform.cpp index b8ee058e7b..ed44cdccb2 100644 --- a/android/jni/com/mapswithme/platform/Platform.cpp +++ b/android/jni/com/mapswithme/platform/Platform.cpp @@ -141,9 +141,8 @@ void Platform::Initialize(JNIEnv * env, jobject functorProcessObject, jstring ap m_resourcesDir = jni::ToNativeString(env, apkPath); m_privateDir = jni::ToNativeString(env, privatePath); m_tmpDir = jni::ToNativeString(env, tmpPath); - m_writableDir = jni::ToNativeString(env, writablePath); + SetWritableDir(jni::ToNativeString(env, writablePath)); LOG(LINFO, ("Apk path = ", m_resourcesDir)); - LOG(LINFO, ("Writable path = ", m_writableDir)); LOG(LINFO, ("Temporary path = ", m_tmpDir)); // IMPORTANT: This method SHOULD be called from UI thread to cache static jni ID-s inside. diff --git a/android/src/com/mapswithme/maps/MwmApplication.java b/android/src/com/mapswithme/maps/MwmApplication.java index 8188d6b9d0..8124afb3d9 100644 --- a/android/src/com/mapswithme/maps/MwmApplication.java +++ b/android/src/com/mapswithme/maps/MwmApplication.java @@ -181,7 +181,7 @@ public class MwmApplication extends Application implements AppBackgroundTracker. final String apkPath = StorageUtils.getApkPath(this); log.d(TAG, "Apk path = " + apkPath); // Note: StoragePathManager uses Config, which requires initConfig() to be called. - final String writablePath = new StoragePathManager().findMapsStorage(this); + final String writablePath = StoragePathManager.findMapsStorage(this); log.d(TAG, "Writable path = " + writablePath); final String privatePath = StorageUtils.getPrivatePath(this); log.d(TAG, "Private path = " + privatePath); diff --git a/android/src/com/mapswithme/maps/settings/StoragePathManager.java b/android/src/com/mapswithme/maps/settings/StoragePathManager.java index fe48fc77b0..4b51b5d183 100644 --- a/android/src/com/mapswithme/maps/settings/StoragePathManager.java +++ b/android/src/com/mapswithme/maps/settings/StoragePathManager.java @@ -146,10 +146,29 @@ public class StoragePathManager return mCurrentStorageIndex; } + /** + * Updates the list of available external storages. + * + * The scan order is following: + * + * 1. Current directory from Config. + * 2. Application-specific directories on shared/external storage devices. + * 3. Application-specific directory on the internal memory. + * + * Directories are checked for the free space and the write access. + * + * @param application Application + */ private void updateExternalStorages(Application application) { List candidates = new ArrayList<>(); + // Current directory. + // Config.getStoragePath() can be empty on the first run. + String configDir = Config.getStoragePath(); + if (!TextUtils.isEmpty(configDir)) + candidates.add(new File(configDir)); + // External storages (SD cards and other). for (File dir : application.getExternalFilesDirs(null)) { @@ -184,82 +203,74 @@ public class StoragePathManager if (internalDir != null) candidates.add(internalDir); - // Configured path. - String configDir = Config.getStoragePath(); - if (!TextUtils.isEmpty(configDir)) - candidates.add(new File(configDir)); - - // Current path. - String currentDir = Framework.nativeGetWritableDir(); - if (!TextUtils.isEmpty(currentDir)) - candidates.add(new File(configDir));; - if (candidates.isEmpty()) throw new AssertionError("Can't find available storage"); // // Update internal state. // + LOGGER.i(TAG, "Begin scanning storages"); mItems.clear(); mCurrentStorageIndex = -1; Set unique = new HashSet<>(); for (File dir : candidates) { - StorageItem item = buildStorageItem(dir); - if (item != null) + try { - String path = item.getFullPath(); - if (!unique.add(path)) + String path = dir.getCanonicalPath(); + // Add the trailing separator because the native code assumes that all paths have it. + if (!path.endsWith(File.separator)) + path = path + File.separator; + + if (!dir.exists() || !dir.isDirectory()) { - // A duplicate - LOGGER.d(TAG, "Skip a duplicate : " + path); + LOGGER.i(TAG, "Rejected " + path + ": not a directory"); continue; } - LOGGER.i(TAG, "Storage found : " + path + ", size : " + item.getFreeSize()); + + final long freeSize = StorageUtils.getFreeBytesAtPath(path); + if (freeSize <= 0) + { + LOGGER.i(TAG, "Rejected " + path + ": not enough space"); + continue; + } + + if (!dir.canWrite() || !StorageUtils.isDirWritable(path)) + { + LOGGER.i(TAG, "Rejected " + path + ": not writable"); + continue; + } + + if (!unique.add(path)) + { + LOGGER.i(TAG, "Rejected " + path + ": a duplicate"); + continue; + } + + StorageItem item = new StorageItem(path, freeSize); if (!TextUtils.isEmpty(configDir) && configDir.equals(path)) { mCurrentStorageIndex = mItems.size(); } + LOGGER.i(TAG, "Accepted " + path + ": " + freeSize + " bytes available"); mItems.add(item); } + catch (IllegalArgumentException | IOException ex) + { + LOGGER.e(TAG, "Rejected " + dir.getPath() + ": error", ex); + continue; + } } + LOGGER.i(TAG, "End scanning storages"); if (!TextUtils.isEmpty(configDir) && mCurrentStorageIndex == -1) { - LOGGER.w(TAG, "Unrecognized current path : " + configDir); + LOGGER.w(TAG, configDir + ": can't find configured directory in the list above!"); for (StorageItem item : mItems) LOGGER.w(TAG, item.toString()); } } - private static StorageItem buildStorageItem(@NonNull File dir) - { - String path = dir.getAbsolutePath(); - LOGGER.d(TAG, "Check storage : " + path); - try - { - path = dir.getCanonicalPath(); - // Add the trailing separator because the native code assumes that all paths have it. - if (!path.endsWith(File.separator)) - path = path + File.separator; - - if (dir.exists() && dir.isDirectory() && dir.canWrite() && StorageUtils.isDirWritable(path)) - { - final long freeSize = StorageUtils.getFreeBytesAtPath(path); - if (freeSize > 0) - { - return new StorageItem(path, freeSize); - } - } - } - catch (IllegalArgumentException | IOException ex) - { - LOGGER.e(TAG, "Can't build storage for path : " + path, ex); - } - - return null; - } - void changeStorage(int newIndex) { final StorageItem oldItem = (mCurrentStorageIndex != -1) ? mItems.get(mCurrentStorageIndex) : null; @@ -350,26 +361,36 @@ public class StoragePathManager candidates[0].list().length > 0); } - public String findMapsStorage(@NonNull Application application) + /** + * Finds a first available storage with existing maps files. + * Returns the best available option if no maps files found. + * See updateExternalStorages() for the scan order details. + */ + public static String findMapsStorage(@NonNull Application application) { - updateExternalStorages(application); + StoragePathManager instance = new StoragePathManager(); + instance.updateExternalStorages(application); - List items = getStorageItems(); + List items = instance.getStorageItems(); + if (items.isEmpty()) + throw new IllegalStateException("No storages found"); for (StorageItem item : items) { - LOGGER.d(TAG, "Scanning: " + item.mPath); if (containsMapData(item.mPath)) { - LOGGER.i(TAG, "Found map at: " + item.mPath); + LOGGER.i(TAG, "Found maps files at " + item.mPath); return item.mPath; } + else + { + LOGGER.i(TAG, "No maps files found at " + item.mPath); + } } // Use the first item by default. final String defaultDir = items.get(0).mPath; LOGGER.i(TAG, "Using default directory: " + defaultDir); - Config.setStoragePath(defaultDir); return defaultDir; } @@ -420,6 +441,9 @@ public class StoragePathManager return NULL_ERROR; } + LOGGER.i(TAG, "Begin moving maps from " + oldStorage.getFullPath() + + " to " + newStorage.getFullPath()); + final File oldDir = new File(oldStorage.getFullPath()); final File newDir = new File(fullNewPath); if (!newDir.exists()) @@ -446,28 +470,22 @@ public class StoragePathManager newFiles[i] = new File(newDir.getAbsolutePath() + File.separator + relPaths.get(i)); } - try + for (int i = 0; i < oldFiles.length; ++i) { - for (int i = 0; i < oldFiles.length; ++i) + LOGGER.i(TAG, "Moving " + oldFiles[i].getPath() + " to " + newFiles[i].getPath()); + File parent = newFiles[i].getParentFile(); + if (parent != null) + parent.mkdirs(); + if (!MapManager.nativeMoveFile(oldFiles[i].getPath(), newFiles[i].getPath())) { - File parent = newFiles[i].getParentFile(); - if (parent != null) - parent.mkdirs(); - if (!MapManager.nativeMoveFile(oldFiles[i].getAbsolutePath(), newFiles[i].getAbsolutePath())) - { - throw new IOException("Failed to move " + oldFiles[i].getAbsolutePath() + " to " + newFiles[i] - .getAbsolutePath()); - } + LOGGER.e(TAG, "Failed to move " + oldFiles[i].getPath() + " to " + newFiles[i].getPath()); + // In the case of failure delete all new files. Old files will + // be lost if new files were just moved from old locations. + StorageUtils.removeFilesInDirectory(newDir, newFiles); + return IOEXCEPTION_ERROR; } } - catch (IOException e) - { - e.printStackTrace(); - // In the case of failure delete all new files. Old files will - // be lost if new files were just moved from old locations. - StorageUtils.removeFilesInDirectory(newDir, newFiles); - return IOEXCEPTION_ERROR; - } + LOGGER.i(TAG, "End moving maps"); UiThread.run(new Runnable() { diff --git a/android/src/com/mapswithme/util/Config.java b/android/src/com/mapswithme/util/Config.java index fa2accb1ba..aec882d311 100644 --- a/android/src/com/mapswithme/util/Config.java +++ b/android/src/com/mapswithme/util/Config.java @@ -131,11 +131,6 @@ public final class Config return getString(KEY_APP_STORAGE); } - public static void setStoragePath(String path) - { - setString(KEY_APP_STORAGE, path); - } - public static boolean isTtsEnabled() { return getBool(KEY_TTS_ENABLED, true);