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,
|
||||
// PTT, Tour-Zähler, Backup/Cover, GPX-Import, Explorer/Routing,
|
||||
// Tour-/Waypoint-Kopien, Hintergrundwiedergabe.
|
||||
versionCode = 100
|
||||
versionName = "2.5.46"
|
||||
versionCode = 102
|
||||
versionName = "2.5.48"
|
||||
}
|
||||
|
||||
val storeFilePath = resolveSigningValue("storeFile", "GPS2AUDIO_RELEASE_STORE_FILE")
|
||||
|
||||
@@ -55,7 +55,7 @@ enum class MapBaseStyle(val key: String) {
|
||||
PERSPECTIVE("perspective");
|
||||
|
||||
companion object {
|
||||
val DEFAULT = MODERN_2D
|
||||
val DEFAULT = CITY_3D
|
||||
fun fromKey(key: String?): MapBaseStyle {
|
||||
return when (key) {
|
||||
"modern_2d" -> MODERN_2D
|
||||
|
||||
@@ -9,6 +9,7 @@ import de.waypointaudio.data.TourAudioSettings
|
||||
import de.waypointaudio.data.TourCover
|
||||
import de.waypointaudio.data.TourPlaybackDefaults
|
||||
import de.waypointaudio.data.TourRoute
|
||||
import de.waypointaudio.data.TrackDraft
|
||||
import de.waypointaudio.data.Waypoint
|
||||
import de.waypointaudio.data.effectiveClips
|
||||
import java.io.BufferedOutputStream
|
||||
@@ -68,6 +69,10 @@ object BackupExportManager {
|
||||
// v2.5.5 — Tour-Cover (Icon + Palette) sind ein reines UI-Detail pro Tour
|
||||
// und wandern mit dem Tour-Export mit.
|
||||
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 appVersionCode: Int = 0
|
||||
)
|
||||
@@ -197,13 +202,17 @@ object BackupExportManager {
|
||||
}
|
||||
|
||||
// ----- 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) {
|
||||
val trackPayload = mapOf(
|
||||
"tracks" to input.gpsTracks
|
||||
"drafts" to input.trackDrafts
|
||||
)
|
||||
writeJsonEntry(zip, "data/track_drafts.json", trackPayload)
|
||||
val pointCount = input.gpsTracks.values.sumOf { it.size }
|
||||
sectionsWritten.add("Track-Entwürfe (${input.gpsTracks.size} Touren, $pointCount Punkte)")
|
||||
val pointCount = input.trackDrafts.sumOf { it.points.size }
|
||||
sectionsWritten.add("Track-Entwürfe (${input.trackDrafts.size} Entwürfe, $pointCount Punkte)")
|
||||
}
|
||||
|
||||
// ----- data/music_settings.json + media/... -----
|
||||
|
||||
@@ -15,6 +15,7 @@ import de.waypointaudio.data.TourAudioSettings
|
||||
import de.waypointaudio.data.TourPlaybackDefaults
|
||||
import de.waypointaudio.data.TourCover
|
||||
import de.waypointaudio.data.TourRoute
|
||||
import de.waypointaudio.data.TrackDraft
|
||||
import de.waypointaudio.data.Waypoint
|
||||
import de.waypointaudio.data.WaypointMusicBehavior
|
||||
import java.io.File
|
||||
@@ -102,6 +103,8 @@ object BackupImportManager {
|
||||
val tourRoutesToWrite: Map<String, TourRoute> = emptyMap(),
|
||||
// v2.5.5 — Tour-Cover, die geschrieben werden sollen (Map endgültiger Tourname → Cover).
|
||||
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 newTourSelection: String?,
|
||||
val warnings: List<String>,
|
||||
@@ -514,42 +517,71 @@ object BackupImportManager {
|
||||
}
|
||||
|
||||
// ---- 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 trackDraftsToAdd = mutableListOf<TrackDraft>()
|
||||
var tracksImported = 0
|
||||
if (selection.trackDrafts && draftsJson != null) {
|
||||
val obj = runCatching {
|
||||
JsonParser.parseString(draftsJson).asJsonObject
|
||||
}.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) ->
|
||||
val mappedTour = tourRename[origTour]
|
||||
?: run {
|
||||
// Tour war nicht im Tour-Import (z. B. wenn nur Drafts importiert werden):
|
||||
// prüfen, ob Name kollidiert.
|
||||
if (origTour == Waypoint.DEFAULT_TOUR_NAME) {
|
||||
origTour
|
||||
} else if (origTour in resultTours) {
|
||||
origTour
|
||||
} else {
|
||||
resultTours.add(origTour)
|
||||
origTour
|
||||
}
|
||||
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]
|
||||
?: run {
|
||||
if (origTour == Waypoint.DEFAULT_TOUR_NAME ||
|
||||
origTour in resultTours
|
||||
) {
|
||||
origTour
|
||||
} else {
|
||||
resultTours.add(origTour)
|
||||
origTour
|
||||
}
|
||||
}
|
||||
// Neue UUID vergeben, damit importierte Drafts nie mit
|
||||
// vorhandenen Drafts-IDs kollidieren.
|
||||
val newDraft = draft.copy(
|
||||
id = java.util.UUID.randomUUID().toString(),
|
||||
sourceTourName = mappedTour,
|
||||
updatedAt = System.currentTimeMillis()
|
||||
)
|
||||
trackDraftsToAdd.add(newDraft)
|
||||
tracksImported++
|
||||
}
|
||||
val existingPoints = existingTracks[mappedTour] ?: emptyList()
|
||||
if (existingPoints.isEmpty()) {
|
||||
// Bestehenden Track NICHT überschreiben.
|
||||
tracksToWrite[mappedTour] = points
|
||||
tracksImported++
|
||||
} 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,
|
||||
tourRoutesToWrite = tourRoutesToWrite,
|
||||
tourCoversToWrite = tourCoversToWrite,
|
||||
// v2.5.48 — Echte TrackDraft-Objekte weitergeben.
|
||||
trackDraftsToAdd = trackDraftsToAdd,
|
||||
trackMinDistanceMeters = trackMinDistanceMeters,
|
||||
newTourSelection = newTourSelection,
|
||||
warnings = warnings,
|
||||
|
||||
@@ -1212,6 +1212,8 @@ class WaypointViewModel(application: Application) : AndroidViewModel(application
|
||||
|
||||
val tourRoutesMap = runCatching { repository.tourRoutes.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(
|
||||
waypoints = _waypoints.value,
|
||||
@@ -1221,6 +1223,7 @@ class WaypointViewModel(application: Application) : AndroidViewModel(application
|
||||
musicSettings = musicSettingsMap,
|
||||
tourRoutes = tourRoutesMap,
|
||||
tourCovers = tourCoversMap,
|
||||
trackDrafts = trackDraftsSnapshot,
|
||||
appVersionName = pkgInfo?.versionName ?: "",
|
||||
appVersionCode = versionCode
|
||||
)
|
||||
@@ -1366,6 +1369,13 @@ class WaypointViewModel(application: Application) : AndroidViewModel(application
|
||||
}
|
||||
}
|
||||
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) ->
|
||||
repository.saveGpsTrack(tour, points)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user