Compare commits

..

1 Commits

Author SHA1 Message Date
zyphlar
1b4e0fa8cc First attempt at allowing Android users to open/import MWMs directly
Signed-off-by: zyphlar <zyphlar@gmail.com>
2026-01-19 15:11:08 -08:00
14 changed files with 602 additions and 7 deletions

View File

@@ -268,7 +268,6 @@ android {
"da",
"de",
"el",
"en-rAu",
"en-rGB",
"es",
"es-rMX",

View File

@@ -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

View File

@@ -331,6 +331,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
}
final IntentProcessor[] mIntentProcessors = {
new Factory.MwmFileProcessor(),
new Factory.UrlProcessor(),
new Factory.KmzKmlProcessor(),
};

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -13,7 +13,6 @@
<locale android:name="da" />
<locale android:name="de" />
<locale android:name="el" />
<locale android:name="en-AU" />
<locale android:name="en-GB" />
<locale android:name="es" />
<locale android:name="es-MX" />

View File

@@ -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);

View File

@@ -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) + "% }";
}
}

View File

@@ -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;
}
}
}

View File

@@ -33,6 +33,7 @@ namespace
char constexpr kBitsExt[] = ".bftsegbits";
char constexpr kNodesExt[] = ".bftsegnodes";
char constexpr kOffsetsExt[] = ".offsets";
char constexpr kCustomMapsDir[] = "custom_maps";
string GetAdditionalWorldScope()
{
@@ -205,6 +206,44 @@ void FindAllDiffs(std::string const & dataDir, std::vector<LocalCountryFile> & d
FindAllDiffsInDirectory(base::JoinPath(dir, fwt.first /* subdir */), diffs);
}
void FindAllCustomMaps(string const & dataDir, std::vector<LocalCountryFile> & localFiles)
{
string const customMapsPath = base::JoinPath(GetDataDirFullPath(dataDir), kCustomMapsDir);
if (!Platform::IsFileExistsByFullPath(customMapsPath) || !Platform::IsDirectory(customMapsPath))
return;
Platform::TFilesWithType versionDirs;
Platform::GetFilesByType(customMapsPath, Platform::EFileType::Directory, versionDirs);
for (auto const & versionDir : versionDirs)
{
string const & subdir = versionDir.first;
int64_t version;
// Custom maps use YYMMDD format for version directories
if (!ParseVersion(subdir, version))
continue;
string const versionPath = base::JoinPath(customMapsPath, subdir);
Platform::TFilesWithType files;
Platform::GetFilesByType(versionPath, Platform::EFileType::Regular, files);
for (auto const & file : files)
{
string name = file.first;
if (!name.ends_with(DATA_FILE_EXTENSION))
continue;
// Remove DATA_FILE_EXTENSION and use base name as a country file name.
base::GetNameWithoutExt(name);
localFiles.emplace_back(versionPath, CountryFile(std::move(name)), version);
}
}
LOG(LINFO, ("Found", localFiles.size(), "custom maps in", customMapsPath));
}
void FindAllLocalMapsAndCleanup(int64_t latestVersion, std::vector<LocalCountryFile> & localFiles)
{
FindAllLocalMapsAndCleanup(latestVersion, string(), localFiles);
@@ -223,6 +262,10 @@ void FindAllLocalMapsAndCleanup(int64_t latestVersion, string const & dataDir,
for (auto const & fwt : fwts)
{
string const & subdir = fwt.first;
// Skip the custom_maps directory - it's processed separately
if (subdir == kCustomMapsDir)
continue;
int64_t version;
if (!ParseVersion(subdir, version) || version > latestVersion)
continue;
@@ -236,6 +279,11 @@ void FindAllLocalMapsAndCleanup(int64_t latestVersion, string const & dataDir,
}
}
// Find custom maps in the custom_maps directory.
// Custom maps have higher priority and use future date versions (YYMMDD format).
// The storage will prefer the newest version when multiple versions exist.
FindAllCustomMaps(dataDir, localFiles);
// Check for World and WorldCoasts in app bundle or in resources.
Platform & platform = GetPlatform();
string const world(WORLD_FILE_NAME);

View File

@@ -46,6 +46,10 @@ void FindAllLocalMapsAndCleanup(int64_t latestVersion, std::string const & dataD
void FindAllDiffs(std::string const & dataDir, std::vector<LocalCountryFile> & diffs);
// Finds custom MWM files in the custom_maps/YYMMDD/ directories.
// Custom maps override downloaded maps when they have a newer version date.
void FindAllCustomMaps(std::string const & dataDir, std::vector<LocalCountryFile> & localFiles);
// This method removes:
// * partially downloaded non-latest maps (with version less than |latestVersion|)
// * empty directories

View File

@@ -1666,6 +1666,9 @@ void Storage::GetNodeAttrs(CountryId const & countryId, NodeAttrs & nodeAttrs) c
nodeAttrs.m_localMwmCounter += 1;
nodeAttrs.m_localMwmSize += localFile->GetSize(MapFileType::Map);
// For leaf nodes, store the local file version
if (d.ChildrenCount() == 0)
nodeAttrs.m_localMwmVersion = localFile->GetVersion();
});
nodeAttrs.m_present = m_localFiles.find(countryId) != m_localFiles.end();

View File

@@ -54,6 +54,7 @@ struct NodeAttrs
, m_mwmSize(0)
, m_localMwmSize(0)
, m_downloadingMwmSize(0)
, m_localMwmVersion(0)
, m_status(NodeStatus::Undefined)
, m_error(NodeErrorCode::NoError)
, m_present(false)
@@ -88,6 +89,10 @@ struct NodeAttrs
/// \note The size of leaves is the size is written in countries.txt.
MwmSize m_downloadingMwmSize;
/// Version of the local mwm file (YYMMDD format, e.g. 251231).
/// 0 if not downloaded.
int64_t m_localMwmVersion;
/// The name of the node in a local language. That means the language dependent on
/// a device locale.
std::string m_nodeLocalName;