Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1189,7 +1189,6 @@ open class RNMBXMapView(private val mContext: Context, var mManager: RNMBXMapVie
}
return null;
}

fun createMapView() : MapView {
var created = false;
mapViewImpl?.also {impl ->
Expand All @@ -1199,6 +1198,10 @@ open class RNMBXMapView(private val mContext: Context, var mManager: RNMBXMapVie
}
}
if (!created) {
val targetPath = resolveMapDataPath(mContext.filesDir)

MapboxMapsOptions.dataPath = targetPath.absolutePath

var options: MapInitOptions? = null
if (surfaceView == false) {
options = MapInitOptions(context = mContext, textureView = true)
Expand Down Expand Up @@ -1598,5 +1601,3 @@ fun OrnamentSettings.setPosAndMargins(posAndMargins: ReadableMap?) {
this.margins = margins
}



Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import com.mapbox.maps.OfflineRegionStatus
import com.mapbox.maps.OfflineRegionTilePyramidDefinition
import com.rnmapbox.rnmbx.utils.ConvertUtils
import com.rnmapbox.rnmbx.utils.extensions.toGeometryCollection
import com.rnmapbox.rnmbx.utils.newMapDataPath
import com.rnmapbox.rnmbx.utils.resolveMapDataPath
import com.rnmapbox.rnmbx.utils.writableArrayOf
import com.rnmapbox.rnmbx.v11compat.offlinemanager.getOfflineRegionManager
import com.mapbox.maps.MapboxMapsOptions
import org.json.JSONException
import org.json.JSONObject
import java.io.File
Expand Down Expand Up @@ -55,10 +58,17 @@ class RNMBXOfflineModuleLegacy(private val mReactContext: ReactApplicationContex
return REACT_CLASS
}

val offlineRegionManager: OfflineRegionManager by lazy {
getOfflineRegionManager {
RNMBXModule.getAccessToken(mReactContext)
@Volatile
private var offlineRegionManager: OfflineRegionManager = createOfflineRegionManager()

private fun createOfflineRegionManager(dataPath: File? = null): OfflineRegionManager {
val targetPath = dataPath ?: resolveMapDataPath(mReactContext.filesDir)
if (!targetPath.exists()) {
targetPath.mkdirs()
}

MapboxMapsOptions.dataPath = targetPath.absolutePath
return getOfflineRegionManager { RNMBXModule.getAccessToken(mReactContext) }
}

private fun makeDefinition(
Expand Down Expand Up @@ -377,9 +387,6 @@ class RNMBXOfflineModuleLegacy(private val mReactContext: ReactApplicationContex
@ReactMethod
fun setTileCountLimit(tileCountLimit: Int) {
UiThreadUtil.runOnUiThread {
val offlineRegionManager = getOfflineRegionManager {
RNMBXModule.getAccessToken(mReactContext)
}
offlineRegionManager.setOfflineMapboxTileCountLimit(tileCountLimit.toLong())
}
}
Expand Down Expand Up @@ -451,4 +458,55 @@ class RNMBXOfflineModuleLegacy(private val mReactContext: ReactApplicationContex
promise.reject(mes)
}
}
}

fun reinitOfflineRegionManager(promise: Promise) {
try {
val targetPath = newMapDataPath(mReactContext.filesDir)
if (!targetPath.exists()) {
targetPath.mkdirs()
}

UiThreadUtil.runOnUiThread {
try {
offlineRegionManager = createOfflineRegionManager(targetPath)
Log.d(LOG_TAG, "reinitOfflineRegionManager: using ${targetPath.absolutePath}")
promise.resolve(null)
} catch (t: Throwable) {
Log.e(LOG_TAG, "reinitOfflineRegionManager: failed", t)
promise.reject("reinitOfflineRegionManager error:", t)
}
}
} catch (t: Throwable) {
Log.e(LOG_TAG, "reinitOfflineRegionManager: failed to prepare path", t)
promise.reject("reinitOfflineRegionManager error:", t)
}
}

@ReactMethod
fun hardResetDatabase(promise: Promise) {
Thread {
try {
val filesDir = mReactContext.filesDir
val defaultMapDataDir = File(filesDir, ".mapbox/map_data")
val customMapDataDir = File(filesDir, ".mapbox_custom")

Log.d(LOG_TAG, "hardResetDatabase: deleting ${defaultMapDataDir.absolutePath} and ${customMapDataDir.absolutePath}")

if (defaultMapDataDir.exists()) {
defaultMapDataDir.deleteRecursively()
}

if (customMapDataDir.exists()) {
customMapDataDir.deleteRecursively()
}

UiThreadUtil.runOnUiThread {
reinitOfflineRegionManager(promise)
}
} catch (t: Throwable) {
Log.e(LOG_TAG, "hardResetDatabase: failed", t)
promise.reject("hardResetDatabase error:", t)
}
}.start()
}
}
26 changes: 26 additions & 0 deletions android/src/main/java/com/rnmapbox/rnmbx/utils/MapDataPath.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.rnmapbox.rnmbx.utils

import java.io.File
import java.util.UUID

fun resolveMapDataPath(filesDir: File): File {
val defaultMapDataDir = File(filesDir, ".mapbox/map_data")
val customRoot = File(filesDir, ".mapbox_custom")

if (customRoot.exists()) {
customRoot.listFiles { entry -> entry.isDirectory }?.forEach { entry ->
val candidateMapData = File(entry, "map_data")
val candidateDb = File(candidateMapData, "map_data.db")

if (candidateDb.exists()) {
return candidateMapData
}
}
}

return defaultMapDataDir
}

fun newMapDataPath(filesDir: File): File {
return File(filesDir, ".mapbox_custom/${UUID.randomUUID()}/map_data")
}
2 changes: 2 additions & 0 deletions ios/RNMBX/Offline/RNMBXOfflineModuleLegacy.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ @interface RCT_EXTERN_MODULE(RNMBXOfflineModuleLegacy, RCTEventEmitter<RCTBridge

RCT_EXTERN_METHOD(migrateOfflineCache:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(hardResetDatabase:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(resetDatabase:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)

@end
113 changes: 109 additions & 4 deletions ios/RNMBX/Offline/RNMBXOfflineModuleLegacy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@ class RNMBXOfflineModuleLegacy: RCTEventEmitter {
final let CompleteRegionDownloadState = 2

lazy var offlineRegionManager: OfflineRegionManager = {
#if RNMBX_11
let dataPathURL = try? RNMBXMapDataPath()

#if RNMBX_11
if let dataPathURL = dataPathURL {
MapboxMapsOptions.dataPath = dataPathURL
}
return OfflineRegionManager()
#else
return OfflineRegionManager(resourceOptions: .init(accessToken: RNMBXModule.accessToken!))
#endif
#else
if let dataPathURL = dataPathURL {
return OfflineRegionManager(
resourceOptions: .init(
accessToken: RNMBXModule.accessToken ?? "",
dataPathURL: dataPathURL
)
)
}
return OfflineRegionManager(resourceOptions: .init(accessToken: RNMBXModule.accessToken ?? ""))
#endif
Comment thread
orca-nazar marked this conversation as resolved.
Resolved
}()

@objc
Expand Down Expand Up @@ -433,4 +446,96 @@ func getRegionByName(name: String, offlineRegions: [OfflineRegion]) -> OfflineRe
reject("migrateOfflineCache error:", error.localizedDescription, error)
}
}

func reinitOfflineRegionManager(
_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
let fileManager = FileManager.default
let targetDir: URL

do {
targetDir = try RNMBXNewMapDataPath()
} catch {
reject("dir_error", error.localizedDescription, error)
return
}

// Ensure directory exists
do {
try fileManager.createDirectory(
at: targetDir,
withIntermediateDirectories: true,
attributes: nil
)
} catch {
NSLog("RNMBXOfflineModuleLegacy: failed to create custom map_data dir: \(error)")
reject("dir_error", "Failed to create custom map_data directory", error)
return
}

DispatchQueue.main.async {
#if RNMBX_11
MapboxMapsOptions.dataPath = targetDir
self.offlineRegionManager = OfflineRegionManager()
#else
self.offlineRegionManager = OfflineRegionManager(
resourceOptions: .init(
accessToken: RNMBXModule.accessToken ?? "",
dataPathURL: targetDir
)
)
#endif
Comment thread
orca-nazar marked this conversation as resolved.
Resolved

NSLog("RNMBXOfflineModuleLegacy: offlineRegionManager reinitialized with \(targetDir.path)")
resolve(nil)
}
}

@objc
func hardResetDatabase(
_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
let fileManager = FileManager.default
let appSupport: URL

do {
appSupport = try RNMBXApplicationSupportDirectory()
} catch {
reject("dir_error", error.localizedDescription, error)
return
}

let defaultMapDataDir = appSupport.appendingPathComponent(
".mapbox/map_data",
isDirectory: true
)
let customMapDataDir = appSupport.appendingPathComponent(
".mapbox_custom",
isDirectory: true
)

DispatchQueue.global(qos: .userInitiated).async {
do {
if fileManager.fileExists(atPath: defaultMapDataDir.path) {
try fileManager.removeItem(at: defaultMapDataDir)
}

if fileManager.fileExists(atPath: customMapDataDir.path) {
try fileManager.removeItem(at: customMapDataDir)
}
} catch {
NSLog("RNMBXOfflineModuleLegacy: hardResetDatabase failed: \(error)")
DispatchQueue.main.async {
reject("hard_reset_error", "Failed to remove Mapbox offline directories", error)
}
return
}

DispatchQueue.main.async {
self.reinitOfflineRegionManager(resolve, reject: reject)
}
}
}
}
56 changes: 56 additions & 0 deletions ios/RNMBX/RNMBXMapDataPath.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation

private let RNMBXMapDataPathErrorDomain = "RNMBXMapDataPath"

func RNMBXApplicationSupportDirectory() throws -> URL {
guard let appSupport = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
).first else {
throw NSError(
domain: RNMBXMapDataPathErrorDomain,
code: 1,
userInfo: [NSLocalizedDescriptionKey: "Application Support directory unavailable"]
)
}

return appSupport
}

func RNMBXMapDataPath() throws -> URL {
let fileManager = FileManager.default
let appSupport = try RNMBXApplicationSupportDirectory()
let defaultDataPath = appSupport.appendingPathComponent(".mapbox/map_data", isDirectory: true)
let customRoot = appSupport.appendingPathComponent(".mapbox_custom", isDirectory: true)

guard fileManager.fileExists(atPath: customRoot.path),
let entries = try? fileManager.contentsOfDirectory(
at: customRoot,
includingPropertiesForKeys: [.isDirectoryKey],
options: []
) else {
return defaultDataPath
}

for entry in entries {
if let isDir = try? entry.resourceValues(forKeys: [.isDirectoryKey]).isDirectory,
isDir == true {
let candidateMapData = entry.appendingPathComponent("map_data", isDirectory: true)
let candidateDb = candidateMapData.appendingPathComponent("map_data.db")

if fileManager.fileExists(atPath: candidateDb.path) {
return candidateMapData
}
}
}

return defaultDataPath
}

func RNMBXNewMapDataPath() throws -> URL {
let appSupport = try RNMBXApplicationSupportDirectory()
return appSupport.appendingPathComponent(
".mapbox_custom/\(UUID().uuidString)/map_data",
isDirectory: true
)
}
16 changes: 11 additions & 5 deletions ios/RNMBX/RNMBXMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,21 @@ open class RNMBXMapView: UIView, RCTInvalidating {
if let mapViewImpl = mapViewImpl, let mapViewInstance = createAndAddMapViewImpl(mapViewImpl, self) {
_mapView = mapViewInstance
} else {
#if RNMBX_11
_mapView = MapView(frame: self.bounds, mapInitOptions: MapInitOptions())
#else
let dataPathURL = try? RNMBXMapDataPath()

let accessToken = RNMBXModule.accessToken
if accessToken == nil {
Logger.log(level: .error, message: "No accessToken set, please call Mapbox.setAccessToken(...)")
}
let resourceOptions = ResourceOptions(accessToken: accessToken ?? "")
#if RNMBX_11
if let dataPathURL = dataPathURL {
MapboxMapsOptions.dataPath = dataPathURL
}
_mapView = MapView(frame: self.bounds, mapInitOptions: MapInitOptions())
#else
let resourceOptions = dataPathURL.map {
ResourceOptions(accessToken: accessToken ?? "", dataPathURL: $0)
} ?? ResourceOptions(accessToken: accessToken ?? "")
_mapView = MapView(frame: frame, mapInitOptions: MapInitOptions(resourceOptions: resourceOptions))
#endif
_mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Expand Down Expand Up @@ -1923,4 +1930,3 @@ class RNMBXPointAnnotationManager : AnnotationInteractionDelegate {
manager.refresh()
}
}

14 changes: 14 additions & 0 deletions src/modules/offline/offlineManagerLegacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ class OfflineManagerLegacy {
await this._initialize(true);
}

/**
* Fully resets offline region database file by deleting both `.mapbox/map_data` and `.mapbox_custom` directories,
* then reinitializes the OfflineRegionManager to point to a new data directory.
*
* Reinitialization is required because the Mapbox SDK does not automatically recreate the `map_data` database after deletion.
* After calling this, all subsequent offline region operations will use a fresh, isolated storage location.
* Unmount active MapView instances before calling this; SDK data path is fixed at MapView creation.
*/
async hardResetDatabase(): Promise<void> {
await MapboxOfflineManager.hardResetDatabase();
this._offlinePacks = {};
await this._initialize(true);
}

/**
* Retrieves all the current offline packs that are stored in the database.
*
Expand Down
Loading