93 lines
3.1 KiB
Dart
93 lines
3.1 KiB
Dart
// lib/remote/remote_client.dart
|
|
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import 'remote_models.dart';
|
|
import 'auth_client.dart';
|
|
|
|
class RemoteJsonClient {
|
|
final Uri indexUri; // es. https://prova.patachina.it/photos/
|
|
final RemoteAuth? auth; // opzionale: se presente, aggiunge Bearer
|
|
|
|
RemoteJsonClient(
|
|
String baseUrl,
|
|
String indexPath, {
|
|
this.auth,
|
|
}) : indexUri = Uri.parse(baseUrl.endsWith('/') ? baseUrl : '$baseUrl/')
|
|
.resolve(indexPath);
|
|
|
|
Future<List<RemotePhotoItem>> fetchAll() async {
|
|
Map<String, String> headers = {};
|
|
if (auth != null) {
|
|
headers = await auth!.authHeaders();
|
|
}
|
|
|
|
// DEBUG: stampa la URL precisa
|
|
// ignore: avoid_print
|
|
print('[remote-client] GET $indexUri');
|
|
|
|
http.Response res;
|
|
try {
|
|
res = await http.get(indexUri, headers: headers).timeout(const Duration(seconds: 20));
|
|
} catch (e) {
|
|
throw Exception('Errore rete su $indexUri: $e');
|
|
}
|
|
|
|
// Retry 1 volta in caso di 401 (token scaduto/invalidato)
|
|
if (res.statusCode == 401 && auth != null) {
|
|
headers = await auth!.refreshAndHeaders();
|
|
res = await http.get(indexUri, headers: headers).timeout(const Duration(seconds: 20));
|
|
}
|
|
|
|
// Follow 30x mantenendo Authorization
|
|
if ({301, 302, 307, 308}.contains(res.statusCode) && res.headers['location'] != null) {
|
|
final loc = res.headers['location']!;
|
|
final redirectUri = indexUri.resolve(loc);
|
|
res = await http.get(redirectUri, headers: headers).timeout(const Duration(seconds: 20));
|
|
}
|
|
if (res.statusCode != 200) {
|
|
final snippet = utf8.decode(res.bodyBytes.take(200).toList());
|
|
throw Exception('HTTP ${res.statusCode} ${res.reasonPhrase} su $indexUri. Body: $snippet');
|
|
}
|
|
|
|
final body = utf8.decode(res.bodyBytes);
|
|
|
|
// Qui siamo espliciti: ci aspettiamo SEMPRE una lista top-level
|
|
final dynamic decoded = json.decode(body);
|
|
if (decoded is! List) {
|
|
throw Exception('JSON inatteso: atteso array top-level, ricevuto ${decoded.runtimeType}');
|
|
}
|
|
|
|
final List<dynamic> rawList = decoded;
|
|
|
|
// --- DIAGNOSTICA: conteggio pattern dai dati del SERVER (non stampo il JSON intero)
|
|
int withOriginal = 0, withoutOriginal = 0, leadingSlash = 0, noLeadingSlash = 0;
|
|
for (final e in rawList) {
|
|
if (e is Map<String, dynamic>) {
|
|
final p = (e['path'] ?? '').toString();
|
|
if (p.startsWith('/')) {
|
|
leadingSlash++;
|
|
} else {
|
|
noLeadingSlash++;
|
|
}
|
|
if (p.contains('/original/')) {
|
|
withOriginal++;
|
|
} else {
|
|
withoutOriginal++;
|
|
}
|
|
}
|
|
}
|
|
// ignore: avoid_print
|
|
print('[remote-client] SERVER paths -> withOriginal=$withOriginal | withoutOriginal=$withoutOriginal | '
|
|
'leadingSlash=$leadingSlash | noLeadingSlash=$noLeadingSlash');
|
|
|
|
// Costruiamo List<RemotePhotoItem>
|
|
final List<RemotePhotoItem> items = rawList.map<RemotePhotoItem>((e) {
|
|
if (e is! Map<String, dynamic>) {
|
|
throw Exception('Elemento JSON non è una mappa: ${e.runtimeType} -> $e');
|
|
}
|
|
return RemotePhotoItem.fromJson(e);
|
|
}).toList();
|
|
|
|
return items;
|
|
}
|
|
}
|