mirror of
https://codeberg.org/comaps/comaps
synced 2026-01-22 11:03:58 +00:00
First attempt at allowing Android users to open/import MWMs directly
Signed-off-by: zyphlar <zyphlar@gmail.com>
This commit is contained in:
@@ -346,6 +346,60 @@
|
||||
<data android:mimeType="text/xml" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Custom MWM map files -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:host="*"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<!-- See http://stackoverflow.com/questions/3400072/pathpattern-to-match-file-extension-does-not-work-if-a-period-exists-elsewhere-i -->
|
||||
<data android:pathPattern="/.*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Duplicate without mimeType for MWM files -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern="/.*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.mwm" />
|
||||
<data android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.MWM" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
||||
@@ -331,6 +331,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
|
||||
}
|
||||
|
||||
final IntentProcessor[] mIntentProcessors = {
|
||||
new Factory.MwmFileProcessor(),
|
||||
new Factory.UrlProcessor(),
|
||||
new Factory.KmzKmlProcessor(),
|
||||
};
|
||||
|
||||
@@ -473,7 +473,16 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
{
|
||||
mName.setText(mItem.name);
|
||||
if (!mItem.isExpandable())
|
||||
UiUtils.setTextAndHideIfEmpty(mSubtitle, mItem.description);
|
||||
{
|
||||
// Show version info for downloaded maps (in "My Maps" mode)
|
||||
String subtitle = mItem.description;
|
||||
if (mMyMapsMode && mItem.present && mItem.localVersion > 0)
|
||||
{
|
||||
String versionStr = formatVersion(mItem.localVersion);
|
||||
subtitle = TextUtils.isEmpty(subtitle) ? "v" + versionStr : subtitle + " • v" + versionStr;
|
||||
}
|
||||
UiUtils.setTextAndHideIfEmpty(mSubtitle, subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
if (mItem.isExpandable())
|
||||
@@ -509,6 +518,21 @@ class DownloaderAdapter extends RecyclerView.Adapter<DownloaderAdapter.ViewHolde
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a version number (YYMMDD) into a readable date string (YY.MM.DD).
|
||||
*/
|
||||
private String formatVersion(long version)
|
||||
{
|
||||
if (version <= 0)
|
||||
return "";
|
||||
String v = String.valueOf(version);
|
||||
// Pad with leading zeros if needed
|
||||
while (v.length() < 6)
|
||||
v = "0" + v;
|
||||
// Format as YY.MM.DD
|
||||
return v.substring(0, 2) + "." + v.substring(2, 4) + "." + v.substring(4, 6);
|
||||
}
|
||||
}
|
||||
|
||||
static class HeaderViewHolder extends BaseInnerViewHolder<String>
|
||||
|
||||
@@ -23,10 +23,12 @@ import app.organicmaps.sdk.routing.RoutingController;
|
||||
import app.organicmaps.sdk.search.SearchEngine;
|
||||
import app.organicmaps.sdk.util.StorageUtils;
|
||||
import app.organicmaps.sdk.util.concurrency.ThreadPool;
|
||||
import app.organicmaps.sdk.downloader.CustomMwmManager;
|
||||
import app.organicmaps.search.SearchActivity;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Factory
|
||||
{
|
||||
@@ -65,6 +67,92 @@ public class Factory
|
||||
}
|
||||
}
|
||||
|
||||
public static class MwmFileProcessor implements IntentProcessor
|
||||
{
|
||||
private static final String MWM_EXTENSION = ".mwm";
|
||||
|
||||
@Override
|
||||
public boolean process(@NonNull Intent intent, @NonNull MwmActivity activity)
|
||||
{
|
||||
if (!Intent.ACTION_VIEW.equals(intent.getAction()))
|
||||
return false;
|
||||
|
||||
final Uri uri = intent.getData();
|
||||
if (uri == null)
|
||||
return false;
|
||||
|
||||
// Check if this is an MWM file
|
||||
if (!isMwmFile(activity, uri))
|
||||
return false;
|
||||
|
||||
// Import the MWM file on a background thread
|
||||
ThreadPool.getStorage().execute(() -> {
|
||||
CustomMwmManager.ImportResult result = CustomMwmManager.importMwmFile(activity, uri);
|
||||
|
||||
// Show result on UI thread
|
||||
activity.runOnUiThread(() -> {
|
||||
switch (result)
|
||||
{
|
||||
case SUCCESS:
|
||||
android.widget.Toast.makeText(activity,
|
||||
activity.getString(app.organicmaps.R.string.custom_mwm_import_success),
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
// Reload maps to include the new custom map
|
||||
Framework.nativeReloadWorldMaps();
|
||||
break;
|
||||
case ERROR_INVALID_FILE:
|
||||
android.widget.Toast.makeText(activity,
|
||||
activity.getString(app.organicmaps.R.string.custom_mwm_import_invalid),
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case ERROR_IO:
|
||||
case ERROR_STORAGE:
|
||||
android.widget.Toast.makeText(activity,
|
||||
activity.getString(app.organicmaps.R.string.custom_mwm_import_error),
|
||||
android.widget.Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isMwmFile(@NonNull MwmActivity activity, @NonNull Uri uri)
|
||||
{
|
||||
String fileName = null;
|
||||
|
||||
// Try to get filename from content resolver
|
||||
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
|
||||
{
|
||||
try (android.database.Cursor cursor = activity.getContentResolver().query(
|
||||
uri, new String[]{android.provider.OpenableColumns.DISPLAY_NAME}, null, null, null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToFirst())
|
||||
{
|
||||
int nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex >= 0)
|
||||
fileName = cursor.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// Fallback to URI path
|
||||
if (fileName == null)
|
||||
{
|
||||
String path = uri.getPath();
|
||||
if (path != null)
|
||||
{
|
||||
int lastSlash = path.lastIndexOf('/');
|
||||
fileName = lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
|
||||
}
|
||||
}
|
||||
|
||||
return fileName != null && fileName.toLowerCase(Locale.US).endsWith(MWM_EXTENSION);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UrlProcessor implements IntentProcessor
|
||||
{
|
||||
private static final int SEARCH_IN_VIEWPORT_ZOOM = 16;
|
||||
|
||||
@@ -975,4 +975,8 @@
|
||||
<string name="download_resources_custom_url_message">Override the default map download server used for map downloads. Leave empty to use CoMaps default server.</string>
|
||||
<string name="download_resources_custom_url_summary_none">Not set</string>
|
||||
<string name="download_resources_custom_url_error_scheme">Please enter a URL starting with http:// or https://</string>
|
||||
<!-- Custom MWM file import messages -->
|
||||
<string name="custom_mwm_import_success">Custom map imported successfully. Restart the app to load it.</string>
|
||||
<string name="custom_mwm_import_invalid">Invalid MWM file. Please select a valid map file.</string>
|
||||
<string name="custom_mwm_import_error">Failed to import the map file. Please try again.</string>
|
||||
</resources>
|
||||
|
||||
@@ -67,8 +67,8 @@ struct CountryItemBuilder
|
||||
jclass m_class;
|
||||
jmethodID m_ctor;
|
||||
jfieldID m_Id, m_Name, m_DirectParentId, m_TopmostParentId, m_DirectParentName, m_TopmostParentName, m_Description,
|
||||
m_Size, m_EnqueuedSize, m_TotalSize, m_ChildCount, m_TotalChildCount, m_Present, m_Progress, m_DownloadedBytes,
|
||||
m_BytesToDownload, m_Category, m_Status, m_ErrorCode;
|
||||
m_Size, m_EnqueuedSize, m_TotalSize, m_LocalVersion, m_ChildCount, m_TotalChildCount, m_Present, m_Progress,
|
||||
m_DownloadedBytes, m_BytesToDownload, m_Category, m_Status, m_ErrorCode;
|
||||
|
||||
CountryItemBuilder(JNIEnv * env)
|
||||
{
|
||||
@@ -85,6 +85,7 @@ struct CountryItemBuilder
|
||||
m_Size = env->GetFieldID(m_class, "size", "J");
|
||||
m_EnqueuedSize = env->GetFieldID(m_class, "enqueuedSize", "J");
|
||||
m_TotalSize = env->GetFieldID(m_class, "totalSize", "J");
|
||||
m_LocalVersion = env->GetFieldID(m_class, "localVersion", "J");
|
||||
m_ChildCount = env->GetFieldID(m_class, "childCount", "I");
|
||||
m_TotalChildCount = env->GetFieldID(m_class, "totalChildCount", "I");
|
||||
m_Present = env->GetFieldID(m_class, "present", "Z");
|
||||
@@ -221,6 +222,9 @@ static void UpdateItem(JNIEnv * env, jobject item, storage::NodeAttrs const & at
|
||||
env->SetLongField(item, ciBuilder.m_EnqueuedSize, attrs.m_downloadingMwmSize);
|
||||
env->SetLongField(item, ciBuilder.m_TotalSize, attrs.m_mwmSize);
|
||||
|
||||
// Local version (YYMMDD format)
|
||||
env->SetLongField(item, ciBuilder.m_LocalVersion, attrs.m_localMwmVersion);
|
||||
|
||||
// Child counts
|
||||
env->SetIntField(item, ciBuilder.m_ChildCount, attrs.m_downloadingMwmCounter);
|
||||
env->SetIntField(item, ciBuilder.m_TotalChildCount, attrs.m_mwmCounter);
|
||||
|
||||
@@ -53,6 +53,9 @@ public final class CountryItem implements Comparable<CountryItem>
|
||||
public long enqueuedSize;
|
||||
public long totalSize;
|
||||
|
||||
// Local MWM file version (YYMMDD format, e.g. 251231). 0 if not downloaded.
|
||||
public long localVersion;
|
||||
|
||||
public int childCount;
|
||||
public int totalChildCount;
|
||||
|
||||
@@ -155,7 +158,7 @@ public final class CountryItem implements Comparable<CountryItem>
|
||||
+ "\", category: \"" + category + "\", name: \"" + name + "\", directParentName: \"" + directParentName
|
||||
+ "\", topmostParentName: \"" + topmostParentName + "\", present: " + present + ", status: " + status
|
||||
+ ", errorCode: " + errorCode + ", headerId: " + headerId + ", size: " + size + ", enqueuedSize: " + enqueuedSize
|
||||
+ ", totalSize: " + totalSize + ", childCount: " + childCount + ", totalChildCount: " + totalChildCount
|
||||
+ ", progress: " + StringUtils.formatUsingUsLocale("%.2f", progress) + "% }";
|
||||
+ ", totalSize: " + totalSize + ", localVersion: " + localVersion + ", childCount: " + childCount
|
||||
+ ", totalChildCount: " + totalChildCount + ", progress: " + StringUtils.formatUsingUsLocale("%.2f", progress) + "% }";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
package app.organicmaps.sdk.downloader;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.organicmaps.sdk.Framework;
|
||||
import app.organicmaps.sdk.util.StorageUtils;
|
||||
import app.organicmaps.sdk.util.log.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages custom MWM map files that are manually imported by the user.
|
||||
* Custom maps are stored in dated folders (YYMMDD format) and take precedence
|
||||
* over downloaded maps when loading.
|
||||
*/
|
||||
public class CustomMwmManager
|
||||
{
|
||||
private static final String TAG = CustomMwmManager.class.getSimpleName();
|
||||
private static final String CUSTOM_MAPS_DIR = "custom_maps";
|
||||
private static final String MWM_EXTENSION = ".mwm";
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyMMdd", Locale.US);
|
||||
|
||||
public enum ImportResult
|
||||
{
|
||||
SUCCESS,
|
||||
ERROR_INVALID_FILE,
|
||||
ERROR_IO,
|
||||
ERROR_STORAGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom MWM file with its metadata.
|
||||
*/
|
||||
public static class CustomMwmFile
|
||||
{
|
||||
public final String name; // e.g., "Portland"
|
||||
public final String path; // Full path to the file
|
||||
public final long version; // Date version as YYMMDD number
|
||||
public final long fileSize;
|
||||
|
||||
public CustomMwmFile(String name, String path, long version, long fileSize)
|
||||
{
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.version = version;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom maps root directory path.
|
||||
*/
|
||||
@NonNull
|
||||
public static String getCustomMapsDir(@NonNull Context context)
|
||||
{
|
||||
String writableDir = Framework.nativeGetWritableDir();
|
||||
return StorageUtils.addTrailingSeparator(writableDir) + CUSTOM_MAPS_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the custom maps directory for today's date.
|
||||
* @return The directory path, or null if creation failed.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getTodayCustomMapsDir(@NonNull Context context)
|
||||
{
|
||||
String customMapsDir = getCustomMapsDir(context);
|
||||
String today = DATE_FORMAT.format(new Date());
|
||||
String todayDir = StorageUtils.addTrailingSeparator(customMapsDir) + today;
|
||||
|
||||
File dir = new File(todayDir);
|
||||
if (!dir.exists() && !dir.mkdirs())
|
||||
{
|
||||
Logger.e(TAG, "Failed to create custom maps directory: " + todayDir);
|
||||
return null;
|
||||
}
|
||||
|
||||
return todayDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an MWM file from a content URI into the custom maps directory.
|
||||
* The file is saved in a dated folder based on today's date.
|
||||
*
|
||||
* @param context Application context
|
||||
* @param uri Content URI of the MWM file
|
||||
* @return ImportResult indicating success or the type of error
|
||||
*/
|
||||
@NonNull
|
||||
public static ImportResult importMwmFile(@NonNull Context context, @NonNull Uri uri)
|
||||
{
|
||||
String fileName = getFileNameFromUri(context, uri);
|
||||
if (fileName == null || !fileName.toLowerCase(Locale.US).endsWith(MWM_EXTENSION))
|
||||
{
|
||||
Logger.e(TAG, "Invalid file name or not an MWM file: " + fileName);
|
||||
return ImportResult.ERROR_INVALID_FILE;
|
||||
}
|
||||
|
||||
String destDir = getTodayCustomMapsDir(context);
|
||||
if (destDir == null)
|
||||
{
|
||||
return ImportResult.ERROR_STORAGE;
|
||||
}
|
||||
|
||||
File destFile = new File(destDir, fileName);
|
||||
Logger.i(TAG, "Importing MWM file to: " + destFile.getAbsolutePath());
|
||||
|
||||
try
|
||||
{
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
if (!StorageUtils.copyFile(resolver, uri, destFile))
|
||||
{
|
||||
Logger.e(TAG, "Failed to copy MWM file");
|
||||
return ImportResult.ERROR_IO;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Successfully imported MWM file: " + fileName);
|
||||
return ImportResult.SUCCESS;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.e(TAG, "IOException while importing MWM file", e);
|
||||
return ImportResult.ERROR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file name from a content URI.
|
||||
*/
|
||||
@Nullable
|
||||
private static String getFileNameFromUri(@NonNull Context context, @NonNull Uri uri)
|
||||
{
|
||||
String fileName = null;
|
||||
|
||||
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
|
||||
{
|
||||
try (android.database.Cursor cursor = context.getContentResolver().query(
|
||||
uri, new String[]{android.provider.OpenableColumns.DISPLAY_NAME}, null, null, null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToFirst())
|
||||
{
|
||||
int nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex >= 0)
|
||||
{
|
||||
fileName = cursor.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.e(TAG, "Failed to get file name from URI", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (fileName == null)
|
||||
{
|
||||
// Try to get from path
|
||||
String path = uri.getPath();
|
||||
if (path != null)
|
||||
{
|
||||
int lastSlash = path.lastIndexOf('/');
|
||||
fileName = lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all custom MWM files, grouped by map name with the newest version taking precedence.
|
||||
* @return Map from country name to CustomMwmFile (newest version)
|
||||
*/
|
||||
@NonNull
|
||||
public static Map<String, CustomMwmFile> getCustomMwmFiles(@NonNull Context context)
|
||||
{
|
||||
Map<String, CustomMwmFile> result = new HashMap<>();
|
||||
String customMapsDir = getCustomMapsDir(context);
|
||||
File rootDir = new File(customMapsDir);
|
||||
|
||||
if (!rootDir.exists() || !rootDir.isDirectory())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
File[] versionDirs = rootDir.listFiles(File::isDirectory);
|
||||
if (versionDirs == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Sort by version descending (newest first)
|
||||
Arrays.sort(versionDirs, (a, b) -> {
|
||||
long versionA = parseVersion(a.getName());
|
||||
long versionB = parseVersion(b.getName());
|
||||
return Long.compare(versionB, versionA);
|
||||
});
|
||||
|
||||
for (File versionDir : versionDirs)
|
||||
{
|
||||
long version = parseVersion(versionDir.getName());
|
||||
if (version <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
File[] mwmFiles = versionDir.listFiles((dir, name) ->
|
||||
name.toLowerCase(Locale.US).endsWith(MWM_EXTENSION));
|
||||
|
||||
if (mwmFiles == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (File mwmFile : mwmFiles)
|
||||
{
|
||||
String name = mwmFile.getName();
|
||||
// Remove .mwm extension to get country name
|
||||
String countryName = name.substring(0, name.length() - MWM_EXTENSION.length());
|
||||
|
||||
// Only add if we don't already have a newer version
|
||||
if (!result.containsKey(countryName))
|
||||
{
|
||||
result.put(countryName, new CustomMwmFile(
|
||||
countryName,
|
||||
mwmFile.getAbsolutePath(),
|
||||
version,
|
||||
mwmFile.length()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all version directories in the custom maps folder, sorted by version descending.
|
||||
* @return List of version directory paths
|
||||
*/
|
||||
@NonNull
|
||||
public static List<String> getCustomMapVersionDirs(@NonNull Context context)
|
||||
{
|
||||
List<String> result = new ArrayList<>();
|
||||
String customMapsDir = getCustomMapsDir(context);
|
||||
File rootDir = new File(customMapsDir);
|
||||
|
||||
if (!rootDir.exists() || !rootDir.isDirectory())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
File[] versionDirs = rootDir.listFiles(File::isDirectory);
|
||||
if (versionDirs == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Sort by version descending (newest first)
|
||||
Arrays.sort(versionDirs, (a, b) -> {
|
||||
long versionA = parseVersion(a.getName());
|
||||
long versionB = parseVersion(b.getName());
|
||||
return Long.compare(versionB, versionA);
|
||||
});
|
||||
|
||||
for (File versionDir : versionDirs)
|
||||
{
|
||||
if (parseVersion(versionDir.getName()) > 0)
|
||||
{
|
||||
result.add(StorageUtils.addTrailingSeparator(versionDir.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a custom version of a map exists.
|
||||
* @param countryName The country/map name (without .mwm extension)
|
||||
* @return The CustomMwmFile if found, null otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public static CustomMwmFile getCustomMwmFile(@NonNull Context context, @NonNull String countryName)
|
||||
{
|
||||
Map<String, CustomMwmFile> customFiles = getCustomMwmFiles(context);
|
||||
return customFiles.get(countryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a custom MWM file.
|
||||
* @param file The CustomMwmFile to delete
|
||||
* @return true if deletion was successful
|
||||
*/
|
||||
public static boolean deleteCustomMwmFile(@NonNull CustomMwmFile file)
|
||||
{
|
||||
File f = new File(file.path);
|
||||
boolean deleted = f.delete();
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
Logger.i(TAG, "Deleted custom MWM file: " + file.path);
|
||||
|
||||
// Clean up empty version directories
|
||||
File parentDir = f.getParentFile();
|
||||
if (parentDir != null)
|
||||
{
|
||||
String[] remaining = parentDir.list();
|
||||
if (remaining != null && remaining.length == 0)
|
||||
{
|
||||
if (parentDir.delete())
|
||||
{
|
||||
Logger.i(TAG, "Deleted empty version directory: " + parentDir.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.e(TAG, "Failed to delete custom MWM file: " + file.path);
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a version string (YYMMDD format) to a long.
|
||||
* @return The version number, or 0 if parsing failed
|
||||
*/
|
||||
private static long parseVersion(String versionStr)
|
||||
{
|
||||
if (versionStr == null || versionStr.length() != 6)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Long.parseLong(versionStr);
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user