diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 51343a1ff..5e78fdc33 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -346,6 +346,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
diff --git a/android/app/src/main/java/app/organicmaps/intent/Factory.java b/android/app/src/main/java/app/organicmaps/intent/Factory.java
index da6c76b20..02847cbbf 100644
--- a/android/app/src/main/java/app/organicmaps/intent/Factory.java
+++ b/android/app/src/main/java/app/organicmaps/intent/Factory.java
@@ -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;
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index b0185acc9..d811ac672 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -975,4 +975,8 @@
Override the default map download server used for map downloads. Leave empty to use CoMaps default server.
Not set
Please enter a URL starting with http:// or https://
+
+ Custom map imported successfully. Restart the app to load it.
+ Invalid MWM file. Please select a valid map file.
+ Failed to import the map file. Please try again.
diff --git a/android/sdk/src/main/cpp/app/organicmaps/sdk/MapManager.cpp b/android/sdk/src/main/cpp/app/organicmaps/sdk/MapManager.cpp
index 6d18e55ef..1efe997e7 100644
--- a/android/sdk/src/main/cpp/app/organicmaps/sdk/MapManager.cpp
+++ b/android/sdk/src/main/cpp/app/organicmaps/sdk/MapManager.cpp
@@ -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);
diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/downloader/CountryItem.java b/android/sdk/src/main/java/app/organicmaps/sdk/downloader/CountryItem.java
index f2ba56517..c8cca3176 100644
--- a/android/sdk/src/main/java/app/organicmaps/sdk/downloader/CountryItem.java
+++ b/android/sdk/src/main/java/app/organicmaps/sdk/downloader/CountryItem.java
@@ -53,6 +53,9 @@ public final class CountryItem implements Comparable
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
+ "\", 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) + "% }";
}
}
diff --git a/android/sdk/src/main/java/app/organicmaps/sdk/downloader/CustomMwmManager.java b/android/sdk/src/main/java/app/organicmaps/sdk/downloader/CustomMwmManager.java
new file mode 100644
index 000000000..641c42d22
--- /dev/null
+++ b/android/sdk/src/main/java/app/organicmaps/sdk/downloader/CustomMwmManager.java
@@ -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 getCustomMwmFiles(@NonNull Context context)
+ {
+ Map 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 getCustomMapVersionDirs(@NonNull Context context)
+ {
+ List 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 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;
+ }
+ }
+
+}
diff --git a/libs/platform/local_country_file_utils.cpp b/libs/platform/local_country_file_utils.cpp
index 8fd5a8844..5febfbfb0 100644
--- a/libs/platform/local_country_file_utils.cpp
+++ b/libs/platform/local_country_file_utils.cpp
@@ -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 & d
FindAllDiffsInDirectory(base::JoinPath(dir, fwt.first /* subdir */), diffs);
}
+void FindAllCustomMaps(string const & dataDir, std::vector & 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 & 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);
diff --git a/libs/platform/local_country_file_utils.hpp b/libs/platform/local_country_file_utils.hpp
index 0cf595e6b..94d55f23e 100644
--- a/libs/platform/local_country_file_utils.hpp
+++ b/libs/platform/local_country_file_utils.hpp
@@ -46,6 +46,10 @@ void FindAllLocalMapsAndCleanup(int64_t latestVersion, std::string const & dataD
void FindAllDiffs(std::string const & dataDir, std::vector & 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 & localFiles);
+
// This method removes:
// * partially downloaded non-latest maps (with version less than |latestVersion|)
// * empty directories
diff --git a/libs/storage/storage.cpp b/libs/storage/storage.cpp
index 3a42039c9..7abc14b87 100644
--- a/libs/storage/storage.cpp
+++ b/libs/storage/storage.cpp
@@ -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();
diff --git a/libs/storage/storage.hpp b/libs/storage/storage.hpp
index 98117be89..5f0034ceb 100644
--- a/libs/storage/storage.hpp
+++ b/libs/storage/storage.hpp
@@ -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;