188 lines
5.6 KiB
Dart
188 lines
5.6 KiB
Dart
import 'dart:convert';
|
||
import 'dart:io';
|
||
import 'package:http/http.dart' as http;
|
||
import '../config/app_config.dart';
|
||
import '../services/auth_service.dart';
|
||
import '../services/melo_logger.dart';
|
||
|
||
/// Cloud-Sync Service für Melo Registry
|
||
/// Authentifizierung via Baka-Auth JWT-Token
|
||
class CloudService {
|
||
static String get _base => AppConfig.cloudUrl;
|
||
String _user = '';
|
||
|
||
/// Login mit Baka-Auth – Token wird aus AuthService bezogen
|
||
Future<bool> login(String user) async {
|
||
_user = user;
|
||
try {
|
||
final r = await http
|
||
.get(Uri.parse('$_base/api/cloud/status'),
|
||
headers: _authHeader)
|
||
.timeout(const Duration(seconds: 5));
|
||
return r.statusCode == 200;
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Map<String, String> get _authHeader {
|
||
final token = AuthService().token;
|
||
final headers = <String, String>{
|
||
'X-API-Key': AppConfig.ytProxyApiKey,
|
||
};
|
||
if (_user.isNotEmpty) {
|
||
headers['X-User'] = _user;
|
||
}
|
||
// Baka-Auth JWT Token mitsenden falls vorhanden
|
||
if (token != null && token.isNotEmpty) {
|
||
headers['Authorization'] = 'Bearer $token';
|
||
}
|
||
return headers;
|
||
}
|
||
|
||
Future<Map?> status() => _get('/api/cloud/status');
|
||
|
||
Future<List<Map>> listSongs() async {
|
||
final r = await _get('/api/cloud/list');
|
||
return List<Map>.from(r?['songs'] ?? []);
|
||
}
|
||
|
||
Future<String?> upload(String filepath, String filename) async {
|
||
try {
|
||
final req = http.MultipartRequest('POST', Uri.parse('$_base/api/cloud/upload'));
|
||
req.headers.addAll(_authHeader);
|
||
req.files.add(await http.MultipartFile.fromPath('file', filepath,
|
||
filename: filename));
|
||
final resp = await req.send().timeout(const Duration(seconds: 120));
|
||
final body = jsonDecode(await resp.stream.bytesToString());
|
||
return body['song_id'] as String?;
|
||
} catch (e) {
|
||
MeloLogger().fehler('cloud_upload', e);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
Future<bool> download(String songId, String destPath) async {
|
||
try {
|
||
final r = await http
|
||
.get(Uri.parse('$_base/api/cloud/download/$songId'),
|
||
headers: _authHeader)
|
||
.timeout(const Duration(seconds: 120));
|
||
if (r.statusCode == 200) {
|
||
await File(destPath).writeAsBytes(r.bodyBytes);
|
||
return true;
|
||
}
|
||
return false;
|
||
} catch (e) {
|
||
MeloLogger().fehler('cloud_download', e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Future<bool> delete(String songId) async {
|
||
try {
|
||
final r = await http
|
||
.post(Uri.parse('$_base/api/cloud/delete'),
|
||
headers: {..._authHeader, 'Content-Type': 'application/json'},
|
||
body: jsonEncode({'song_id': songId}))
|
||
.timeout(const Duration(seconds: 10));
|
||
return r.statusCode == 200;
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Future<Map?> _get(String path) async {
|
||
try {
|
||
final r = await http
|
||
.get(Uri.parse('$_base$path'), headers: _authHeader)
|
||
.timeout(const Duration(seconds: 10));
|
||
if (r.statusCode == 200) return jsonDecode(r.body);
|
||
} catch (_) {}
|
||
return null;
|
||
}
|
||
|
||
Future<String?> share(List<String> songIds) async {
|
||
try {
|
||
final r = await http
|
||
.post(Uri.parse('$_base/api/cloud/share'),
|
||
headers: {..._authHeader, 'Content-Type': 'application/json'},
|
||
body: jsonEncode({'song_ids': songIds}))
|
||
.timeout(const Duration(seconds: 10));
|
||
if (r.statusCode == 200) {
|
||
final d = jsonDecode(r.body);
|
||
return d['code'] as String?;
|
||
}
|
||
} catch (_) {}
|
||
return null;
|
||
}
|
||
|
||
Future<Map?> importCode(String code) async {
|
||
try {
|
||
final r = await http
|
||
.post(Uri.parse('$_base/api/cloud/import'),
|
||
headers: {..._authHeader, 'Content-Type': 'application/json'},
|
||
body: jsonEncode({'code': code}))
|
||
.timeout(const Duration(seconds: 10));
|
||
if (r.statusCode == 200) return jsonDecode(r.body);
|
||
} catch (_) {}
|
||
return null;
|
||
}
|
||
|
||
Future<List<Map>> syncChanges(String since) async {
|
||
try {
|
||
final r = await http
|
||
.post(Uri.parse('$_base/api/cloud/sync'),
|
||
headers: {..._authHeader, 'Content-Type': 'application/json'},
|
||
body: jsonEncode({'since': since}))
|
||
.timeout(const Duration(seconds: 10));
|
||
if (r.statusCode == 200) {
|
||
final d = jsonDecode(r.body);
|
||
return List<Map>.from(d['changes'] ?? []);
|
||
}
|
||
} catch (_) {}
|
||
return [];
|
||
}
|
||
|
||
Future<List<Map>> globalList() async {
|
||
try {
|
||
final r = await http
|
||
.get(Uri.parse('$_base/api/cloud/global'), headers: _authHeader)
|
||
.timeout(const Duration(seconds: 10));
|
||
if (r.statusCode == 200) {
|
||
return List<Map>.from(jsonDecode(r.body)['songs'] ?? []);
|
||
}
|
||
} catch (_) {}
|
||
return [];
|
||
}
|
||
|
||
Future<bool> toggleGlobal(String songId) async {
|
||
try {
|
||
final r = await http
|
||
.post(Uri.parse('$_base/api/cloud/toggle-global'),
|
||
headers: {..._authHeader, 'Content-Type': 'application/json'},
|
||
body: jsonEncode({'song_id': songId}))
|
||
.timeout(const Duration(seconds: 10));
|
||
return r.statusCode == 200;
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// Ruft defekte/korrupte Songs vom Cloud-Server ab
|
||
Future<List<Map>> getCorrupted() async {
|
||
try {
|
||
final r = await http
|
||
.get(Uri.parse('$_base/api/cloud/corrupted'), headers: _authHeader)
|
||
.timeout(const Duration(seconds: 15));
|
||
if (r.statusCode == 200) {
|
||
final d = jsonDecode(r.body);
|
||
return List<Map>.from(d['corrupted'] ?? []);
|
||
}
|
||
} catch (e) {
|
||
MeloLogger().fehler('cloud_corrupted', e);
|
||
}
|
||
return [];
|
||
}
|
||
}
|