refactor(server): calculate asset type server side (#3200)

* refactor(server): calculate asset type server-side

* chore: open api

* chore: remove comments

* fix: linting

* update

* Revert "update"

This reverts commit dc58702923250b9385d22468a7afe77dc9972a03.

* fix: upload LivePhotos

* chore: remove unused request fields for upload

* remove unused method

* mobile-fix: livePhoto filename

* fix: revert check for livephotos filename and extension

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen
2023-07-11 23:56:30 -04:00
committed by GitHub
parent 93462aafbc
commit b71d7e33bb
24 changed files with 97 additions and 580 deletions

View File

@@ -14,15 +14,13 @@ import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/utils/files_helper.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:http_parser/http_parser.dart';
import 'package:path/path.dart' as p;
import 'package:cancellation_token_http/http.dart' as http;
import 'package:path/path.dart' as p;
final backupServiceProvider = Provider(
(ref) => BackupService(
@@ -230,18 +228,12 @@ class BackupService {
if (file != null) {
String originalFileName = await entity.titleAsync;
var fileExtension = p.extension(file.path);
var mimeType = FileHelper.getMimeType(file.path);
var fileStream = file.openRead();
var assetRawUploadData = http.MultipartFile(
"assetData",
fileStream,
file.lengthSync(),
filename: originalFileName,
contentType: MediaType(
mimeType["type"],
mimeType["subType"],
),
);
var req = MultipartRequest(
@@ -256,12 +248,10 @@ class BackupService {
req.fields['deviceAssetId'] = entity.id;
req.fields['deviceId'] = deviceId;
req.fields['assetType'] = _getAssetType(entity.type);
req.fields['fileCreatedAt'] = entity.createDateTime.toIso8601String();
req.fields['fileModifiedAt'] =
entity.modifiedDateTime.toIso8601String();
req.fields['isFavorite'] = entity.isFavorite.toString();
req.fields['fileExtension'] = fileExtension;
req.fields['duration'] = entity.videoDuration.toString();
req.files.add(assetRawUploadData);
@@ -342,18 +332,12 @@ class BackupService {
var validPath = motionFilePath.replaceAll('file://', '');
var motionFile = File(validPath);
var fileStream = motionFile.openRead();
String originalFileName = await entity.titleAsync;
var mimeType = FileHelper.getMimeType(validPath);
String fileName = p.basename(motionFile.path);
return http.MultipartFile(
"livePhotoData",
fileStream,
motionFile.lengthSync(),
filename: originalFileName,
contentType: MediaType(
mimeType["type"],
mimeType["subType"],
),
filename: fileName,
);
}

View File

@@ -1,7 +1,6 @@
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/modules/partner/services/partner.service.dart';
import 'package:immich_mobile/shared/models/store.dart';
@@ -11,7 +10,6 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/shared/services/sync.service.dart';
import 'package:immich_mobile/utils/diff.dart';
import 'package:immich_mobile/utils/files_helper.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
@@ -59,17 +57,11 @@ class UserService {
Future<CreateProfileImageResponseDto?> uploadProfileImage(XFile image) async {
try {
var mimeType = FileHelper.getMimeType(image.path);
return await _apiService.userApi.createProfileImage(
MultipartFile.fromBytes(
'file',
await image.readAsBytes(),
filename: image.name,
contentType: MediaType(
mimeType["type"],
mimeType["subType"],
),
),
);
} catch (e) {

View File

@@ -1,150 +0,0 @@
import 'package:path/path.dart' as p;
class FileHelper {
static getMimeType(String filePath) {
var fileExtension = p.extension(filePath).split(".")[1];
switch (fileExtension.toLowerCase()) {
case 'gif':
return {"type": "image", "subType": "gif"};
case 'jpeg':
return {"type": "image", "subType": "jpeg"};
case 'jpg':
return {"type": "image", "subType": "jpeg"};
case 'png':
return {"type": "image", "subType": "png"};
case 'tif':
return {"type": "image", "subType": "tiff"};
case 'mov':
return {"type": "video", "subType": "quicktime"};
case 'mp4':
return {"type": "video", "subType": "mp4"};
case 'avi':
return {"type": "video", "subType": "x-msvideo"};
case 'heic':
return {"type": "image", "subType": "heic"};
case 'heif':
return {"type": "image", "subType": "heif"};
case 'dng':
return {"type": "image", "subType": "dng"};
case 'webp':
return {"type": "image", "subType": "webp"};
case '3gp':
return {"type": "video", "subType": "3gpp"};
case 'webm':
return {"type": "video", "subType": "webm"};
case 'avif':
return {"type": "image", "subType": "avif"};
case 'insp':
return {"type": "image", "subType": "jpeg"};
case 'insv':
return {"type": "video", "subType": "mp4"};
case 'arw':
return {"type": "image", "subType": "x-sony-arw"};
case 'raf':
return {"type": "image", "subType": "x-fuji-raf"};
case 'nef':
return {"type": "image", "subType": "x-nikon-nef"};
case 'srw':
return {"type": "image", "subType": "x-samsung-srw"};
case 'crw':
return {"type": "image", "subType": "x-canon-crw"};
case 'cr2':
return {"type": "image", "subType": "x-canon-cr2"};
case 'cr3':
return {"type": "image", "subType": "x-canon-cr3"};
case 'erf':
return {"type": "image", "subType": "x-epson-erf"};
case 'dcr':
return {"type": "image", "subType": "x-kodak-dcr"};
case 'k25':
return {"type": "image", "subType": "x-kodak-k25"};
case 'kdc':
return {"type": "image", "subType": "x-kodak-kdc"};
case 'mrw':
return {"type": "image", "subType": "x-minolta-mrw"};
case 'orf':
return {"type": "image", "subType": "x-olympus-orf"};
case 'raw':
return {"type": "image", "subType": "x-panasonic-raw"};
case 'pef':
return {"type": "image", "subType": "x-panasonic-pef"};
case 'x3f':
return {"type": "image", "subType": "x-sigma-x3f"};
case 'srf':
return {"type": "image", "subType": "x-sony-srf"};
case 'sr2':
return {"type": "image", "subType": "x-sony-sr2"};
case '3fr':
return {"type": "image", "subType": "x-hasselblad-3fr"};
case 'fff':
return {"type": "image", "subType": "x-hasselblad-fff"};
case 'rwl':
return {"type": "image", "subType": "x-leica-rwl"};
case 'ori':
return {"type": "image", "subType": "x-olympus-ori"};
case 'iiq':
return {"type": "image", "subType": "x-phaseone-iiq"};
case 'ari':
return {"type": "image", "subType": "x-arriflex-ari"};
case 'cap':
return {"type": "image", "subType": "x-phaseone-cap"};
case 'cin':
return {"type": "image", "subType": "x-phantom-cin"};
case 'jxl':
return {"type": "image", "subType": "jxl"};
case 'mts':
return {"type": "video", "subType": "mp2t"};
case 'm2ts':
return {"type": "video", "subType": "mp2t"};
default:
return {"type": "unsupport", "subType": "unsupport"};
}
}
}

View File

@@ -1393,7 +1393,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **uploadFile**
> AssetFileUploadResponseDto uploadFile(assetType, assetData, fileExtension, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, livePhotoData, sidecarData, isReadOnly, isArchived, isVisible, duration)
> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, livePhotoData, sidecarData, isReadOnly, isArchived, isVisible, duration)
@@ -1416,9 +1416,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetType = ; // AssetTypeEnum |
final assetData = BINARY_DATA_HERE; // MultipartFile |
final fileExtension = fileExtension_example; // String |
final deviceAssetId = deviceAssetId_example; // String |
final deviceId = deviceId_example; // String |
final fileCreatedAt = 2013-10-20T19:20:30+01:00; // DateTime |
@@ -1433,7 +1431,7 @@ final isVisible = true; // bool |
final duration = duration_example; // String |
try {
final result = api_instance.uploadFile(assetType, assetData, fileExtension, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, livePhotoData, sidecarData, isReadOnly, isArchived, isVisible, duration);
final result = api_instance.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, livePhotoData, sidecarData, isReadOnly, isArchived, isVisible, duration);
print(result);
} catch (e) {
print('Exception when calling AssetApi->uploadFile: $e\n');
@@ -1444,9 +1442,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetType** | [**AssetTypeEnum**](AssetTypeEnum.md)| |
**assetData** | **MultipartFile**| |
**fileExtension** | **String**| |
**deviceAssetId** | **String**| |
**deviceId** | **String**| |
**fileCreatedAt** | **DateTime**| |

View File

@@ -8,7 +8,6 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetType** | [**AssetTypeEnum**](AssetTypeEnum.md) | |
**isReadOnly** | **bool** | | [optional] [default to true]
**assetPath** | **String** | |
**sidecarPath** | **String** | | [optional]

View File

@@ -1359,12 +1359,8 @@ class AssetApi {
/// Performs an HTTP 'POST /asset/upload' operation and returns the [Response].
/// Parameters:
///
/// * [AssetTypeEnum] assetType (required):
///
/// * [MultipartFile] assetData (required):
///
/// * [String] fileExtension (required):
///
/// * [String] deviceAssetId (required):
///
/// * [String] deviceId (required):
@@ -1388,7 +1384,7 @@ class AssetApi {
/// * [bool] isVisible:
///
/// * [String] duration:
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String fileExtension, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isReadOnly, bool? isArchived, bool? isVisible, String? duration, }) async {
Future<Response> uploadFileWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isReadOnly, bool? isArchived, bool? isVisible, String? duration, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/upload';
@@ -1407,10 +1403,6 @@ class AssetApi {
bool hasFields = false;
final mp = MultipartRequest('POST', Uri.parse(path));
if (assetType != null) {
hasFields = true;
mp.fields[r'assetType'] = parameterToString(assetType);
}
if (assetData != null) {
hasFields = true;
mp.fields[r'assetData'] = assetData.field;
@@ -1430,10 +1422,6 @@ class AssetApi {
hasFields = true;
mp.fields[r'isReadOnly'] = parameterToString(isReadOnly);
}
if (fileExtension != null) {
hasFields = true;
mp.fields[r'fileExtension'] = parameterToString(fileExtension);
}
if (deviceAssetId != null) {
hasFields = true;
mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId);
@@ -1483,12 +1471,8 @@ class AssetApi {
/// Parameters:
///
/// * [AssetTypeEnum] assetType (required):
///
/// * [MultipartFile] assetData (required):
///
/// * [String] fileExtension (required):
///
/// * [String] deviceAssetId (required):
///
/// * [String] deviceId (required):
@@ -1512,8 +1496,8 @@ class AssetApi {
/// * [bool] isVisible:
///
/// * [String] duration:
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String fileExtension, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isReadOnly, bool? isArchived, bool? isVisible, String? duration, }) async {
final response = await uploadFileWithHttpInfo(assetType, assetData, fileExtension, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key: key, livePhotoData: livePhotoData, sidecarData: sidecarData, isReadOnly: isReadOnly, isArchived: isArchived, isVisible: isVisible, duration: duration, );
Future<AssetFileUploadResponseDto?> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String? key, MultipartFile? livePhotoData, MultipartFile? sidecarData, bool? isReadOnly, bool? isArchived, bool? isVisible, String? duration, }) async {
final response = await uploadFileWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key: key, livePhotoData: livePhotoData, sidecarData: sidecarData, isReadOnly: isReadOnly, isArchived: isArchived, isVisible: isVisible, duration: duration, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -13,7 +13,6 @@ part of openapi.api;
class ImportAssetDto {
/// Returns a new [ImportAssetDto] instance.
ImportAssetDto({
required this.assetType,
this.isReadOnly = true,
required this.assetPath,
this.sidecarPath,
@@ -27,8 +26,6 @@ class ImportAssetDto {
this.duration,
});
AssetTypeEnum assetType;
bool isReadOnly;
String assetPath;
@@ -77,7 +74,6 @@ class ImportAssetDto {
@override
bool operator ==(Object other) => identical(this, other) || other is ImportAssetDto &&
other.assetType == assetType &&
other.isReadOnly == isReadOnly &&
other.assetPath == assetPath &&
other.sidecarPath == sidecarPath &&
@@ -93,7 +89,6 @@ class ImportAssetDto {
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetType.hashCode) +
(isReadOnly.hashCode) +
(assetPath.hashCode) +
(sidecarPath == null ? 0 : sidecarPath!.hashCode) +
@@ -107,11 +102,10 @@ class ImportAssetDto {
(duration == null ? 0 : duration!.hashCode);
@override
String toString() => 'ImportAssetDto[assetType=$assetType, isReadOnly=$isReadOnly, assetPath=$assetPath, sidecarPath=$sidecarPath, deviceAssetId=$deviceAssetId, deviceId=$deviceId, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, isFavorite=$isFavorite, isArchived=$isArchived, isVisible=$isVisible, duration=$duration]';
String toString() => 'ImportAssetDto[isReadOnly=$isReadOnly, assetPath=$assetPath, sidecarPath=$sidecarPath, deviceAssetId=$deviceAssetId, deviceId=$deviceId, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, isFavorite=$isFavorite, isArchived=$isArchived, isVisible=$isVisible, duration=$duration]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetType'] = this.assetType;
json[r'isReadOnly'] = this.isReadOnly;
json[r'assetPath'] = this.assetPath;
if (this.sidecarPath != null) {
@@ -150,7 +144,6 @@ class ImportAssetDto {
final json = value.cast<String, dynamic>();
return ImportAssetDto(
assetType: AssetTypeEnum.fromJson(json[r'assetType'])!,
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly') ?? true,
assetPath: mapValueOfType<String>(json, r'assetPath')!,
sidecarPath: mapValueOfType<String>(json, r'sidecarPath'),
@@ -209,7 +202,6 @@ class ImportAssetDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetType',
'assetPath',
'deviceAssetId',
'deviceId',

View File

@@ -151,7 +151,7 @@ void main() {
// TODO
});
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String fileExtension, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String key, MultipartFile livePhotoData, MultipartFile sidecarData, bool isReadOnly, bool isArchived, bool isVisible, String duration }) async
//Future<AssetFileUploadResponseDto> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, bool isFavorite, { String key, MultipartFile livePhotoData, MultipartFile sidecarData, bool isReadOnly, bool isArchived, bool isVisible, String duration }) async
test('test uploadFile', () async {
// TODO
});

View File

@@ -16,11 +16,6 @@ void main() {
// final instance = ImportAssetDto();
group('test ImportAssetDto', () {
// AssetTypeEnum assetType
test('to test the property `assetType`', () async {
// TODO
});
// bool isReadOnly (default value: true)
test('to test the property `isReadOnly`', () async {
// TODO