Release GPS2Audio v2.5.48 TrackDraft backup fix
This commit is contained in:
@@ -157,8 +157,8 @@ android {
|
|||||||
// • Keine Regressionen: Phantom-Layer-Fix, Lizenzsystem, Multi-Clip,
|
// • Keine Regressionen: Phantom-Layer-Fix, Lizenzsystem, Multi-Clip,
|
||||||
// PTT, Tour-Zähler, Backup/Cover, GPX-Import, Explorer/Routing,
|
// PTT, Tour-Zähler, Backup/Cover, GPX-Import, Explorer/Routing,
|
||||||
// Tour-/Waypoint-Kopien, Hintergrundwiedergabe.
|
// Tour-/Waypoint-Kopien, Hintergrundwiedergabe.
|
||||||
versionCode = 100
|
versionCode = 102
|
||||||
versionName = "2.5.46"
|
versionName = "2.5.48"
|
||||||
}
|
}
|
||||||
|
|
||||||
val storeFilePath = resolveSigningValue("storeFile", "GPS2AUDIO_RELEASE_STORE_FILE")
|
val storeFilePath = resolveSigningValue("storeFile", "GPS2AUDIO_RELEASE_STORE_FILE")
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ enum class MapBaseStyle(val key: String) {
|
|||||||
PERSPECTIVE("perspective");
|
PERSPECTIVE("perspective");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT = MODERN_2D
|
val DEFAULT = CITY_3D
|
||||||
fun fromKey(key: String?): MapBaseStyle {
|
fun fromKey(key: String?): MapBaseStyle {
|
||||||
return when (key) {
|
return when (key) {
|
||||||
"modern_2d" -> MODERN_2D
|
"modern_2d" -> MODERN_2D
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import de.waypointaudio.data.TourAudioSettings
|
|||||||
import de.waypointaudio.data.TourCover
|
import de.waypointaudio.data.TourCover
|
||||||
import de.waypointaudio.data.TourPlaybackDefaults
|
import de.waypointaudio.data.TourPlaybackDefaults
|
||||||
import de.waypointaudio.data.TourRoute
|
import de.waypointaudio.data.TourRoute
|
||||||
|
import de.waypointaudio.data.TrackDraft
|
||||||
import de.waypointaudio.data.Waypoint
|
import de.waypointaudio.data.Waypoint
|
||||||
import de.waypointaudio.data.effectiveClips
|
import de.waypointaudio.data.effectiveClips
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
@@ -68,6 +69,10 @@ object BackupExportManager {
|
|||||||
// v2.5.5 — Tour-Cover (Icon + Palette) sind ein reines UI-Detail pro Tour
|
// v2.5.5 — Tour-Cover (Icon + Palette) sind ein reines UI-Detail pro Tour
|
||||||
// und wandern mit dem Tour-Export mit.
|
// und wandern mit dem Tour-Export mit.
|
||||||
val tourCovers: Map<String, TourCover> = emptyMap(),
|
val tourCovers: Map<String, TourCover> = emptyMap(),
|
||||||
|
// v2.5.48 — Echte TrackDraft-Objekte (Name, Notiz, Markierungen, Punkte).
|
||||||
|
// gpsTracks bleibt für die Live-Aufzeichnung weiterhin im Backup-Input,
|
||||||
|
// wird aber NICHT mehr in track_drafts.json geschrieben.
|
||||||
|
val trackDrafts: List<TrackDraft> = emptyList(),
|
||||||
val appVersionName: String = "",
|
val appVersionName: String = "",
|
||||||
val appVersionCode: Int = 0
|
val appVersionCode: Int = 0
|
||||||
)
|
)
|
||||||
@@ -197,13 +202,17 @@ object BackupExportManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----- data/track_drafts.json -----
|
// ----- data/track_drafts.json -----
|
||||||
|
// v2.5.48 FIX: Exportiert die echten TrackDraft-Objekte (Name, Notiz,
|
||||||
|
// Markierungen, Punkte) statt der gpsTracks-Map. Altes Format
|
||||||
|
// ("tracks": { tourName: [GpsTrackPoint] }) wird beim Import
|
||||||
|
// weiterhin toleriert (Abwärtskompatibilität).
|
||||||
if (selection.trackDrafts) {
|
if (selection.trackDrafts) {
|
||||||
val trackPayload = mapOf(
|
val trackPayload = mapOf(
|
||||||
"tracks" to input.gpsTracks
|
"drafts" to input.trackDrafts
|
||||||
)
|
)
|
||||||
writeJsonEntry(zip, "data/track_drafts.json", trackPayload)
|
writeJsonEntry(zip, "data/track_drafts.json", trackPayload)
|
||||||
val pointCount = input.gpsTracks.values.sumOf { it.size }
|
val pointCount = input.trackDrafts.sumOf { it.points.size }
|
||||||
sectionsWritten.add("Track-Entwürfe (${input.gpsTracks.size} Touren, $pointCount Punkte)")
|
sectionsWritten.add("Track-Entwürfe (${input.trackDrafts.size} Entwürfe, $pointCount Punkte)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- data/music_settings.json + media/... -----
|
// ----- data/music_settings.json + media/... -----
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import de.waypointaudio.data.TourAudioSettings
|
|||||||
import de.waypointaudio.data.TourPlaybackDefaults
|
import de.waypointaudio.data.TourPlaybackDefaults
|
||||||
import de.waypointaudio.data.TourCover
|
import de.waypointaudio.data.TourCover
|
||||||
import de.waypointaudio.data.TourRoute
|
import de.waypointaudio.data.TourRoute
|
||||||
|
import de.waypointaudio.data.TrackDraft
|
||||||
import de.waypointaudio.data.Waypoint
|
import de.waypointaudio.data.Waypoint
|
||||||
import de.waypointaudio.data.WaypointMusicBehavior
|
import de.waypointaudio.data.WaypointMusicBehavior
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -102,6 +103,8 @@ object BackupImportManager {
|
|||||||
val tourRoutesToWrite: Map<String, TourRoute> = emptyMap(),
|
val tourRoutesToWrite: Map<String, TourRoute> = emptyMap(),
|
||||||
// v2.5.5 — Tour-Cover, die geschrieben werden sollen (Map endgültiger Tourname → Cover).
|
// v2.5.5 — Tour-Cover, die geschrieben werden sollen (Map endgültiger Tourname → Cover).
|
||||||
val tourCoversToWrite: Map<String, TourCover> = emptyMap(),
|
val tourCoversToWrite: Map<String, TourCover> = emptyMap(),
|
||||||
|
// v2.5.48 — Echte TrackDraft-Objekte, die als neue Drafts hinzugefügt werden sollen.
|
||||||
|
val trackDraftsToAdd: List<TrackDraft> = emptyList(),
|
||||||
val trackMinDistanceMeters: Int?,
|
val trackMinDistanceMeters: Int?,
|
||||||
val newTourSelection: String?,
|
val newTourSelection: String?,
|
||||||
val warnings: List<String>,
|
val warnings: List<String>,
|
||||||
@@ -514,42 +517,71 @@ object BackupImportManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Track-Entwürfe ----
|
// ---- Track-Entwürfe ----
|
||||||
|
// v2.5.48 FIX: Importiert echte TrackDraft-Objekte aus dem "drafts"-Array.
|
||||||
|
// Abwärtskompatibilität: Alte Backups schrieben fälschlicherweise die
|
||||||
|
// gpsTracks-Map unter "tracks" in track_drafts.json. Falls "drafts" fehlt
|
||||||
|
// aber "tracks" vorhanden ist, wird das alte Format erkannt und toleriert
|
||||||
|
// (kein Crash, keine Datenverluste an bestehenden Touren/Waypoints).
|
||||||
val tracksToWrite = mutableMapOf<String, List<GpsTrackPoint>>()
|
val tracksToWrite = mutableMapOf<String, List<GpsTrackPoint>>()
|
||||||
|
val trackDraftsToAdd = mutableListOf<TrackDraft>()
|
||||||
var tracksImported = 0
|
var tracksImported = 0
|
||||||
if (selection.trackDrafts && draftsJson != null) {
|
if (selection.trackDrafts && draftsJson != null) {
|
||||||
val obj = runCatching {
|
val obj = runCatching {
|
||||||
JsonParser.parseString(draftsJson).asJsonObject
|
JsonParser.parseString(draftsJson).asJsonObject
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
val map: Map<String, List<GpsTrackPoint>> = if (obj != null) {
|
|
||||||
runCatching {
|
|
||||||
gson.fromJson<Map<String, List<GpsTrackPoint>>>(
|
|
||||||
obj.get("tracks"),
|
|
||||||
object : TypeToken<Map<String, List<GpsTrackPoint>>>() {}.type
|
|
||||||
) ?: emptyMap()
|
|
||||||
}.getOrDefault(emptyMap())
|
|
||||||
} else emptyMap()
|
|
||||||
|
|
||||||
map.forEach { (origTour, points) ->
|
if (obj != null) {
|
||||||
|
// Neues Format (v2.5.48+): "drafts" enthält eine Liste echter TrackDraft-Objekte.
|
||||||
|
val hasDraftsKey = obj.has("drafts") && !obj.get("drafts").isJsonNull
|
||||||
|
|
||||||
|
if (hasDraftsKey) {
|
||||||
|
// Neues Format: Liste von TrackDraft-Objekten
|
||||||
|
val importedDrafts: List<TrackDraft> = runCatching {
|
||||||
|
gson.fromJson<List<TrackDraft>>(
|
||||||
|
obj.get("drafts"),
|
||||||
|
object : TypeToken<List<TrackDraft>>() {}.type
|
||||||
|
) ?: emptyList()
|
||||||
|
}.getOrDefault(emptyList())
|
||||||
|
|
||||||
|
importedDrafts.forEach { draft ->
|
||||||
|
// Tourname-Mapping anwenden (für den Fall, dass Tour umbenannt wurde).
|
||||||
|
val origTour = draft.sourceTourName
|
||||||
val mappedTour = tourRename[origTour]
|
val mappedTour = tourRename[origTour]
|
||||||
?: run {
|
?: run {
|
||||||
// Tour war nicht im Tour-Import (z. B. wenn nur Drafts importiert werden):
|
if (origTour == Waypoint.DEFAULT_TOUR_NAME ||
|
||||||
// prüfen, ob Name kollidiert.
|
origTour in resultTours
|
||||||
if (origTour == Waypoint.DEFAULT_TOUR_NAME) {
|
) {
|
||||||
origTour
|
|
||||||
} else if (origTour in resultTours) {
|
|
||||||
origTour
|
origTour
|
||||||
} else {
|
} else {
|
||||||
resultTours.add(origTour)
|
resultTours.add(origTour)
|
||||||
origTour
|
origTour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val existingPoints = existingTracks[mappedTour] ?: emptyList()
|
// Neue UUID vergeben, damit importierte Drafts nie mit
|
||||||
if (existingPoints.isEmpty()) {
|
// vorhandenen Drafts-IDs kollidieren.
|
||||||
// Bestehenden Track NICHT überschreiben.
|
val newDraft = draft.copy(
|
||||||
tracksToWrite[mappedTour] = points
|
id = java.util.UUID.randomUUID().toString(),
|
||||||
|
sourceTourName = mappedTour,
|
||||||
|
updatedAt = System.currentTimeMillis()
|
||||||
|
)
|
||||||
|
trackDraftsToAdd.add(newDraft)
|
||||||
tracksImported++
|
tracksImported++
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
warnings.add("Track-Entwurf für Tour „$mappedTour\" bereits vorhanden – nicht überschrieben.")
|
// Altes (fehlerhaftes) Format: "tracks" enthält gpsTracks-Map.
|
||||||
|
// Tolerieren ohne Crash, aber nicht in Tracks oder Drafts importieren,
|
||||||
|
// da der Inhalt GPS-Track-Daten (nicht Draft-Metadaten) sind.
|
||||||
|
// Hinweis in Warnings ausgeben.
|
||||||
|
val hasTracksKey = obj.has("tracks") && !obj.get("tracks").isJsonNull
|
||||||
|
if (hasTracksKey) {
|
||||||
|
warnings.add(
|
||||||
|
"Hinweis: track_drafts.json aus älterem Backup-Format erkannt " +
|
||||||
|
"(enthält GPS-Tracks statt Draft-Objekte). " +
|
||||||
|
"TrackDraft-Namen/Notizen können nicht wiederhergestellt werden. " +
|
||||||
|
"Bestehende Touren und Wegpunkte bleiben unverändert."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Keine Daten importieren aus altem, fehlerh. Format — kein Crash.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -636,6 +668,8 @@ object BackupImportManager {
|
|||||||
musicToWrite = musicToWrite,
|
musicToWrite = musicToWrite,
|
||||||
tourRoutesToWrite = tourRoutesToWrite,
|
tourRoutesToWrite = tourRoutesToWrite,
|
||||||
tourCoversToWrite = tourCoversToWrite,
|
tourCoversToWrite = tourCoversToWrite,
|
||||||
|
// v2.5.48 — Echte TrackDraft-Objekte weitergeben.
|
||||||
|
trackDraftsToAdd = trackDraftsToAdd,
|
||||||
trackMinDistanceMeters = trackMinDistanceMeters,
|
trackMinDistanceMeters = trackMinDistanceMeters,
|
||||||
newTourSelection = newTourSelection,
|
newTourSelection = newTourSelection,
|
||||||
warnings = warnings,
|
warnings = warnings,
|
||||||
|
|||||||
@@ -1212,6 +1212,8 @@ class WaypointViewModel(application: Application) : AndroidViewModel(application
|
|||||||
|
|
||||||
val tourRoutesMap = runCatching { repository.tourRoutes.first() }.getOrDefault(emptyMap())
|
val tourRoutesMap = runCatching { repository.tourRoutes.first() }.getOrDefault(emptyMap())
|
||||||
val tourCoversMap = runCatching { repository.tourCovers.first() }.getOrDefault(emptyMap())
|
val tourCoversMap = runCatching { repository.tourCovers.first() }.getOrDefault(emptyMap())
|
||||||
|
// v2.5.48 — Echte TrackDraft-Objekte für Backup lesen (nicht gpsTracks).
|
||||||
|
val trackDraftsSnapshot = runCatching { repository.trackDrafts.first() }.getOrDefault(emptyList())
|
||||||
|
|
||||||
val input = de.waypointaudio.io.BackupExportManager.BackupInput(
|
val input = de.waypointaudio.io.BackupExportManager.BackupInput(
|
||||||
waypoints = _waypoints.value,
|
waypoints = _waypoints.value,
|
||||||
@@ -1221,6 +1223,7 @@ class WaypointViewModel(application: Application) : AndroidViewModel(application
|
|||||||
musicSettings = musicSettingsMap,
|
musicSettings = musicSettingsMap,
|
||||||
tourRoutes = tourRoutesMap,
|
tourRoutes = tourRoutesMap,
|
||||||
tourCovers = tourCoversMap,
|
tourCovers = tourCoversMap,
|
||||||
|
trackDrafts = trackDraftsSnapshot,
|
||||||
appVersionName = pkgInfo?.versionName ?: "",
|
appVersionName = pkgInfo?.versionName ?: "",
|
||||||
appVersionCode = versionCode
|
appVersionCode = versionCode
|
||||||
)
|
)
|
||||||
@@ -1366,6 +1369,13 @@ class WaypointViewModel(application: Application) : AndroidViewModel(application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trackDrafts) {
|
if (trackDrafts) {
|
||||||
|
// v2.5.48 FIX: Echte TrackDraft-Objekte importieren statt gpsTracks-Map.
|
||||||
|
// trackDraftsToAdd enthält bereits neue UUIDs (vergeben im ImportManager).
|
||||||
|
payload.trackDraftsToAdd.forEach { draft ->
|
||||||
|
repository.addTrackDraft(draft)
|
||||||
|
}
|
||||||
|
// tracksToWrite ist für die Live-GPS-Track-Map (gpsTracksFlow).
|
||||||
|
// Bei neuem Format leer; bei altem Format auch leer (kein Import).
|
||||||
payload.tracksToWrite.forEach { (tour, points) ->
|
payload.tracksToWrite.forEach { (tour, points) ->
|
||||||
repository.saveGpsTrack(tour, points)
|
repository.saveGpsTrack(tour, points)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user