import 'dart:io'; import 'package:get/get.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:terepi_seged/services/layer_import_service.dart'; import 'package:uuid/uuid.dart'; import 'app_database.dart'; import '../models/imported_layer_meta.dart'; /// Megosztott referencia-rétegek (pl. gázhálózat nyomvonala) kezelése. /// /// Modell: a réteg egy FÁJL (GeoJSON) a Supabase Storage-ban + egy /// metaadat-sor a ts_layers táblában, verziószámmal. /// /// * PULL: minden tagsági projekt rétegeinél a verziószámot hasonlítjuk a /// lokálishoz — csak akkor töltünk le, ha nőtt. A letöltött fájl a /// meglévő imported_layers mechanizmusba kerül, így a térkép ugyanúgy /// jeleníti meg, mint a kézzel importált rétegeket. /// * OFFLINE: a lokálisan cache-elt fájl net nélkül is ott van — a réteg /// mindig a legutóbb letöltött állapotában látszik. /// * PUBLISH: egy lokálisan importált réteg megosztása = fájl feltöltése /// a Storage-ba + ts_layers sor (verzió +1 újrapublikáláskor). class LayerSyncService extends GetxService { static LayerSyncService get to => Get.find(); static const bucket = 'terepi_seged-layers'; final isSyncing = false.obs; SupabaseClient get _client => Supabase.instance.client; AppDatabase get _db => AppDatabase.instance; /// A rétegfájlok lokális cache-könyvtára. Future _layerDir() async { final base = await getExternalStorageDirectory() ?? await getApplicationDocumentsDirectory(); final dir = Directory(p.join(base.path, 'layers')); if (!await dir.exists()) await dir.create(recursive: true); return dir; } // ═════════════════════════════════════════════════════════════════ // PULL — a TsSyncService hívja minden szinkron végén // ═════════════════════════════════════════════════════════════════ Future pullAll() async { if (_client.auth.currentUser == null) return; if (isSyncing.value) return; isSyncing.value = true; try { // Csak a szinkronizált (nem lokális) projektek rétegei érdekelnek. final projects = await _db.listProjects(); for (final project in projects.where((pr) => !pr.isLocalOnly)) { await _pullProjectLayers(project.id!, project.uuid); } // Ha közben egy csapattárs frissített/megosztott egy réteget, ez // jelenjen meg a térképen azonnal, újraindítás nélkül. if (Get.isRegistered()) { await LayerImportService.to.reload(); } } finally { isSyncing.value = false; } } Future _pullProjectLayers( int localProjectId, String projectUuid) async { final rows = await _client .from('terepi_seged_layers') .select() .eq('project_id', projectUuid) .filter('deleted_at', 'is', null); for (final r in rows) { final id = r['id'] as String; final remoteVersion = (r['version'] as int?) ?? 1; final localVersion = await _db.getImportedLayerVersion(id); // Már megvan a legfrissebb verzió → nincs teendő. if (localVersion >= remoteVersion) continue; // Letöltés a Storage-ból, lokális cache-be. final storagePath = r['storage_path'] as String; final bytes = await _client.storage.from(bucket).download(storagePath); final dir = await _layerDir(); final localPath = p.join(dir.path, '$id.geojson'); await File(localPath).writeAsBytes(bytes, flush: true); // Metaadat upsert a meglévő imported_layers táblába — a térkép // rétegkezelője innentől ugyanúgy látja, mint a kézi importot. await _db.upsertImportedLayerRaw({ 'id': id, 'name': r['name'], 'source_type': r['source_type'] ?? 'geojson', 'local_path': localPath, 'storage_path': storagePath, 'is_visible': 1, 'project_id': localProjectId, 'imported_at': r['created_at'] ?? DateTime.now().toIso8601String(), 'synced_at': DateTime.now().toIso8601String(), 'version': remoteVersion, }); } } // ═════════════════════════════════════════════════════════════════ // PUBLISH — lokális réteg megosztása a projekt tagjaival // ═════════════════════════════════════════════════════════════════ /// Feltölti a réteg fájlját és létrehozza/frissíti a ts_layers sorát. /// Újrapublikáláskor a verziószám nő → a többi eszköz a következő /// szinkronnál automatikusan frissíti a példányát. Future publishLayer({ required ImportedLayerMeta layer, required String projectUuid, }) async { final id = layer.id.isNotEmpty ? layer.id : const Uuid().v4(); final fileName = p.basename(layer.localPath); final storagePath = '$projectUuid/$id/$fileName'; // 1. Fájl a Storage-ba (upsert: újrapublikálásnál felülírja). await _client.storage.from(bucket).upload( storagePath, File(layer.localPath), fileOptions: const FileOptions(upsert: true), ); // 2. Verzió léptetése, ha már létezik a sor. final existing = await _client .from('terepi_seged_layers') .select('version') .eq('id', id) .maybeSingle(); final newVersion = ((existing?['version'] as int?) ?? 0) + 1; await _client.from('terepi_seged_layers').upsert({ 'id': id, 'project_id': projectUuid, 'name': layer.name, 'source_type': layer.sourceType.name, 'storage_path': storagePath, 'version': newVersion, }); // 3. Lokális meta frissítése, hogy a saját eszköz ne töltse le újra. await _db.upsertImportedLayerRaw({ ...layer.toMap(), 'id': id, 'storage_path': storagePath, 'synced_at': DateTime.now().toIso8601String(), 'version': newVersion, }); } /// Réteg visszavonása a megosztásból (soft delete a szerveren; /// a lokális cache-példányok megmaradnak az eszközökön). Future unpublishLayer(String layerId) async { await _client.from('terepi_seged_layers').update({ 'deleted_at': DateTime.now().toUtc().toIso8601String(), }).eq('id', layerId); } }