Files
b0esche_cloud/b0esche_cloud/lib/services/file_service.dart

229 lines
6.2 KiB
Dart

import '../models/file_item.dart';
import '../models/viewer_session.dart';
import '../models/editor_session.dart';
import '../models/annotation.dart';
import 'api_client.dart';
import 'package:dio/dio.dart';
class FileService {
final ApiClient _apiClient;
FileService(this._apiClient);
String get baseUrl => _apiClient.baseUrl;
Future<List<FileItem>> getFiles(String orgId, String path) async {
if (path.isEmpty) {
throw Exception('Path cannot be empty');
}
final pathParam = {'path': path};
if (orgId.isEmpty) {
return await _apiClient.getList(
'/user/files',
queryParameters: pathParam,
fromJson: (data) => FileItem(
id: data['id'],
name: data['name'],
path: data['path'],
type: data['type'] == 'file' ? FileType.file : FileType.folder,
size: data['size'],
lastModified: DateTime.parse(data['lastModified']),
),
);
}
return await _apiClient.getList(
'/orgs/$orgId/files',
queryParameters: pathParam,
fromJson: (data) => FileItem(
id: data['id'],
name: data['name'],
path: data['path'],
type: data['type'] == 'file' ? FileType.file : FileType.folder,
size: data['size'],
lastModified: DateTime.parse(data['lastModified']),
),
);
}
Future<FileItem?> getFile(String orgId, String path) async {
throw UnimplementedError();
}
Future<void> uploadFile(String orgId, FileItem file) async {
// If bytes or localPath available, send multipart upload with field 'file'
final Map<String, dynamic> fields = {'path': file.path};
FormData formData;
if (file.bytes != null) {
formData = FormData.fromMap({
...fields,
'file': MultipartFile.fromBytes(file.bytes!, filename: file.name),
});
} else if (file.localPath != null) {
formData = FormData.fromMap({
...fields,
'file': MultipartFile.fromFile(file.localPath!, filename: file.name),
});
} else {
// Fallback to metadata-only create (folders or client that can't send file content)
final data = {
'name': file.name,
'path': file.path,
'type': file.type == FileType.file ? 'file' : 'folder',
'size': file.size,
};
if (orgId.isEmpty) {
await _apiClient.post('/user/files', data: data, fromJson: (d) => null);
return;
}
await _apiClient.post(
'/orgs/$orgId/files',
data: data,
fromJson: (d) => null,
);
return;
}
final endpoint = orgId.isEmpty ? '/user/files' : '/orgs/$orgId/files';
await _apiClient.post(endpoint, data: formData, fromJson: (d) => null);
}
Future<void> deleteFile(String orgId, String path) async {
final data = {'path': path};
if (orgId.isEmpty) {
await _apiClient.post(
'/user/files/delete',
data: data,
fromJson: (d) => null,
);
return;
}
await _apiClient.post(
'/orgs/$orgId/files/delete',
data: data,
fromJson: (d) => null,
);
}
Future<String> getDownloadUrl(String orgId, String path) async {
// Return the download URL for the file
if (orgId.isEmpty) {
return '/user/files/download?path=${Uri.encodeComponent(path)}';
}
return '/orgs/$orgId/files/download?path=${Uri.encodeComponent(path)}';
}
Future<void> createFolder(
String orgId,
String parentPath,
String folderName,
) async {
// Normalize folder name to avoid accidental leading slashes creating double-slash paths
final normalizedName = folderName
.replaceAll(RegExp(r'^/+'), '')
.replaceAll(RegExp(r'/+$'), '');
if (normalizedName.isEmpty) {
throw Exception('Folder name cannot be empty');
}
// Construct proper path: /parent/folder or /folder for root
String path;
if (parentPath == '/') {
path = '/$normalizedName';
} else {
// Ensure parentPath doesn't end with / before appending
final cleanParent = parentPath.endsWith('/')
? parentPath.substring(0, parentPath.length - 1)
: parentPath;
path = '$cleanParent/$normalizedName';
}
final data = {
'name': normalizedName,
'path': path,
'type': 'folder',
'size': 0,
};
if (orgId.isEmpty) {
await _apiClient.post('/user/files', data: data, fromJson: (d) => null);
return;
}
await _apiClient.post(
'/orgs/$orgId/files',
data: data,
fromJson: (d) => null,
);
}
Future<void> moveFile(
String orgId,
String sourcePath,
String targetPath,
) async {
final endpoint = orgId.isEmpty
? '/user/files/move'
: '/orgs/$orgId/files/move';
await _apiClient.post(
endpoint,
data: {'sourcePath': sourcePath, 'targetPath': targetPath},
fromJson: (d) => null,
);
}
Future<void> renameFile(String orgId, String path, String newName) async {
throw UnimplementedError();
}
Future<List<FileItem>> searchFiles(String orgId, String query) async {
throw UnimplementedError();
}
Future<ViewerSession> requestViewerSession(
String orgId,
String fileId,
) async {
if (fileId.isEmpty) {
throw Exception('fileId cannot be empty');
}
final path = orgId.isEmpty
? '/user/files/$fileId/view'
: '/orgs/$orgId/files/$fileId/view';
return await _apiClient.get(
path,
fromJson: (data) => ViewerSession.fromJson(data),
);
}
Future<EditorSession> requestEditorSession(
String orgId,
String fileId,
) async {
if (orgId.isEmpty || fileId.isEmpty) {
throw Exception('OrgId and fileId cannot be empty');
}
return await _apiClient.get(
'/orgs/$orgId/files/$fileId/edit',
fromJson: (data) => EditorSession.fromJson(data),
);
}
Future<void> saveAnnotations(
String orgId,
String fileId,
List<Annotation> annotations,
) async {
if (orgId.isEmpty || fileId.isEmpty) {
throw Exception('OrgId and fileId cannot be empty');
}
await _apiClient.post(
'/orgs/$orgId/files/$fileId/annotations',
data: {
'annotations': annotations.map((a) => a.toJson()).toList(),
'baseVersionId': '1', // mock
},
fromJson: (data) => null,
);
}
}