Merge branch 'feature/readonly-sharing' of github.com:mgabor3141/immich; branch 'main' of github.com:immich-app/immich into feature/readonly-sharing
This commit is contained in:
@@ -52,6 +52,7 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
.putBoolean(ContentObserverWorker.SHARED_PREF_SERVICE_ENABLED, true)
|
||||
.putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long)
|
||||
.putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args.get(1) as String)
|
||||
.putString(BackupWorker.SHARED_PREF_SERVER_URL, args.get(3) as String)
|
||||
.apply()
|
||||
ContentObserverWorker.enable(ctx, immediate = args.get(2) as Boolean)
|
||||
result.success(true)
|
||||
|
||||
@@ -11,8 +11,8 @@ import android.os.PowerManager
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.concurrent.futures.CallbackToFutureAdapter
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.concurrent.futures.ResolvableFuture
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ForegroundInfo
|
||||
@@ -30,6 +30,16 @@ import io.flutter.embedding.engine.loader.FlutterLoader
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.view.FlutterCallbackInformation
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.InetAddress
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@@ -42,7 +52,6 @@ import java.util.concurrent.TimeUnit
|
||||
*/
|
||||
class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ctx, params), MethodChannel.MethodCallHandler {
|
||||
|
||||
private val resolvableFuture = ResolvableFuture.create<Result>()
|
||||
private var engine: FlutterEngine? = null
|
||||
private lateinit var backgroundChannel: MethodChannel
|
||||
private val notificationManager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
@@ -52,37 +61,82 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||
private var notificationDetailBuilder: NotificationCompat.Builder? = null
|
||||
private var fgFuture: ListenableFuture<Void>? = null
|
||||
|
||||
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
||||
private val job = Job()
|
||||
private lateinit var completer: CallbackToFutureAdapter.Completer<Result>
|
||||
private val resolvableFuture = CallbackToFutureAdapter.getFuture { completer ->
|
||||
this.completer = completer
|
||||
null
|
||||
}
|
||||
|
||||
init {
|
||||
resolvableFuture.addListener(
|
||||
Runnable {
|
||||
if (resolvableFuture.isCancelled) {
|
||||
job.cancel()
|
||||
}
|
||||
},
|
||||
taskExecutor.serialTaskExecutor
|
||||
)
|
||||
}
|
||||
|
||||
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
|
||||
Log.d(TAG, "startWork")
|
||||
|
||||
val ctx = applicationContext
|
||||
val prefs = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
if (!flutterLoader.initialized()) {
|
||||
flutterLoader.startInitialization(ctx)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Create a Notification channel if necessary
|
||||
createChannel()
|
||||
}
|
||||
if (isIgnoringBatteryOptimizations) {
|
||||
// normal background services can only up to 10 minutes
|
||||
// foreground services are allowed to run indefinitely
|
||||
// requires battery optimizations to be disabled (either manually by the user
|
||||
// or by the system learning that immich is important to the user)
|
||||
val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!!
|
||||
showInfo(getInfoBuilder(title, indeterminate=true).build())
|
||||
}
|
||||
engine = FlutterEngine(ctx)
|
||||
|
||||
flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||
runDart()
|
||||
}
|
||||
|
||||
prefs.getString(SHARED_PREF_SERVER_URL, null)
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let { serverUrl -> doCoroutineWork(serverUrl) }
|
||||
?: doWork()
|
||||
return resolvableFuture
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to check if server URL is reachable before starting the backup work.
|
||||
* Check must be done in a background to avoid blocking the main thread.
|
||||
*/
|
||||
private fun doCoroutineWork(serverUrl : String) {
|
||||
CoroutineScope(Dispatchers.Default + job).launch {
|
||||
val isReachable = isUrlReachableHttp(serverUrl)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (isReachable) {
|
||||
doWork()
|
||||
} else {
|
||||
// Fail when the URL is not reachable
|
||||
completer.set(Result.failure())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doWork() {
|
||||
Log.d(TAG, "doWork")
|
||||
val ctx = applicationContext
|
||||
|
||||
if (!flutterLoader.initialized()) {
|
||||
flutterLoader.startInitialization(ctx)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// Create a Notification channel if necessary
|
||||
createChannel()
|
||||
}
|
||||
if (isIgnoringBatteryOptimizations) {
|
||||
// normal background services can only up to 10 minutes
|
||||
// foreground services are allowed to run indefinitely
|
||||
// requires battery optimizations to be disabled (either manually by the user
|
||||
// or by the system learning that immich is important to the user)
|
||||
val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!!
|
||||
showInfo(getInfoBuilder(title, indeterminate=true).build())
|
||||
}
|
||||
engine = FlutterEngine(ctx)
|
||||
|
||||
flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) {
|
||||
runDart()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
|
||||
* `background.service.dart` to run the actual backup logic.
|
||||
@@ -139,7 +193,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||
engine = null
|
||||
if (result != null) {
|
||||
Log.d(TAG, "stopEngine result=${result}")
|
||||
resolvableFuture.set(result)
|
||||
this.completer.set(result)
|
||||
}
|
||||
waitOnSetForegroundAsync()
|
||||
}
|
||||
@@ -270,6 +324,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||
const val SHARED_PREF_CALLBACK_KEY = "callbackDispatcherHandle"
|
||||
const val SHARED_PREF_NOTIFICATION_TITLE = "notificationTitle"
|
||||
const val SHARED_PREF_LAST_CHANGE = "lastChange"
|
||||
const val SHARED_PREF_SERVER_URL = "serverUrl"
|
||||
|
||||
private const val TASK_NAME_BACKUP = "immich/BackupWorker"
|
||||
private const val NOTIFICATION_CHANNEL_ID = "immich/backgroundService"
|
||||
@@ -360,3 +415,26 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
|
||||
}
|
||||
|
||||
private const val TAG = "BackupWorker"
|
||||
|
||||
/**
|
||||
* Check if the given URL is reachable via HTTP
|
||||
*/
|
||||
suspend fun isUrlReachableHttp(url: String, timeoutMillis: Long = 5000L): Boolean {
|
||||
return withTimeoutOrNull(timeoutMillis) {
|
||||
var httpURLConnection: HttpURLConnection? = null
|
||||
try {
|
||||
httpURLConnection = (URL(url).openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "HEAD"
|
||||
connectTimeout = timeoutMillis.toInt()
|
||||
readTimeout = timeoutMillis.toInt()
|
||||
}
|
||||
httpURLConnection.connect()
|
||||
httpURLConnection.responseCode == HttpURLConnection.HTTP_OK
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to reach server URL: $e")
|
||||
false
|
||||
} finally {
|
||||
httpURLConnection?.disconnect()
|
||||
}
|
||||
} == true
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ SPEC CHECKSUMS:
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
||||
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
|
||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||
@@ -180,4 +180,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
@@ -171,9 +171,9 @@ class BackgroundServicePlugin: NSObject, FlutterPlugin {
|
||||
return
|
||||
}
|
||||
|
||||
// Requires 3 arguments in the array
|
||||
guard args.count == 3 else {
|
||||
print("Requires 3 arguments and received \(args.count)")
|
||||
// Requires 3 or more arguments in the array
|
||||
guard args.count >= 3 else {
|
||||
print("Requires 3 or more arguments and received \(args.count)")
|
||||
result(FlutterMethodNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/backup_progress.dart';
|
||||
import 'package:immich_mobile/utils/diff.dart';
|
||||
import 'package:immich_mobile/utils/url_helper.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:path_provider_ios/path_provider_ios.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
@@ -68,8 +69,10 @@ class BackgroundService {
|
||||
final callback = PluginUtilities.getCallbackHandle(_nativeEntry)!;
|
||||
final String title =
|
||||
"backup_background_service_default_notification".tr();
|
||||
final bool ok = await _foregroundChannel
|
||||
.invokeMethod('enable', [callback.toRawHandle(), title, immediate]);
|
||||
final bool ok = await _foregroundChannel.invokeMethod(
|
||||
'enable',
|
||||
[callback.toRawHandle(), title, immediate, getServerUrl()],
|
||||
);
|
||||
return ok;
|
||||
} catch (error) {
|
||||
return false;
|
||||
|
||||
@@ -181,10 +181,21 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
UserResponseDto? userResponseDto;
|
||||
try {
|
||||
userResponseDto = await _apiService.userApi.getMyUserInfo();
|
||||
} on ApiException catch (e) {
|
||||
if (e.innerException is SocketException) {
|
||||
} on ApiException catch (error, stackTrace) {
|
||||
_log.severe(
|
||||
"Error getting user information from the server [API EXCEPTION]",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
if (error.innerException is SocketException) {
|
||||
state = state.copyWith(isAuthenticated: true);
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
_log.severe(
|
||||
"Error getting user information from the server [CATCH ALL]",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
if (userResponseDto != null) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/oauth.provider.dart';
|
||||
@@ -86,6 +87,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
context: context,
|
||||
msg: e.message ?? 'login_form_api_exception'.tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
isOauthEnable.value = false;
|
||||
isPasswordLoginEnable.value = true;
|
||||
@@ -96,6 +98,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
context: context,
|
||||
msg: 'login_form_handshake_exception'.tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
isOauthEnable.value = false;
|
||||
isPasswordLoginEnable.value = true;
|
||||
@@ -106,6 +109,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
context: context,
|
||||
msg: 'login_form_server_error'.tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
isOauthEnable.value = false;
|
||||
isPasswordLoginEnable.value = true;
|
||||
@@ -174,6 +178,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
context: context,
|
||||
msg: "login_form_failed_login".tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
@@ -197,6 +202,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
context: context,
|
||||
msg: "login_form_failed_get_oauth_server_config".tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
isLoading.value = false;
|
||||
return;
|
||||
@@ -225,6 +231,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
context: context,
|
||||
msg: "login_form_failed_login".tr(),
|
||||
toastType: ToastType.error,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -235,6 +242,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
context: context,
|
||||
msg: "login_form_failed_get_oauth_server_disable".tr(),
|
||||
toastType: ToastType.info,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
isLoading.value = false;
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:immich_mobile/shared/services/server_info.service.dart';
|
||||
import 'package:immich_mobile/shared/models/server_info/server_config.model.dart';
|
||||
import 'package:immich_mobile/shared/models/server_info/server_features.model.dart';
|
||||
import 'package:immich_mobile/shared/models/server_info/server_version.model.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
||||
@@ -47,6 +48,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
||||
);
|
||||
|
||||
final ServerInfoService _serverInfoService;
|
||||
final _log = Logger("ServerInfoNotifier");
|
||||
|
||||
Future<void> getServerInfo() async {
|
||||
await getServerVersion();
|
||||
@@ -55,17 +57,25 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
|
||||
}
|
||||
|
||||
getServerVersion() async {
|
||||
final serverVersion = await _serverInfoService.getServerVersion();
|
||||
try {
|
||||
final serverVersion = await _serverInfoService.getServerVersion();
|
||||
|
||||
if (serverVersion == null) {
|
||||
if (serverVersion == null) {
|
||||
state = state.copyWith(
|
||||
isVersionMismatch: true,
|
||||
versionMismatchErrorMessage: "common_server_error".tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await _checkServerVersionMismatch(serverVersion);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe("Failed to get server version", e, stackTrace);
|
||||
state = state.copyWith(
|
||||
isVersionMismatch: true,
|
||||
versionMismatchErrorMessage: "common_server_error".tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await _checkServerVersionMismatch(serverVersion);
|
||||
}
|
||||
|
||||
_checkServerVersionMismatch(ServerVersion serverVersion) async {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/utils/url_helper.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
@@ -34,6 +35,7 @@ class ApiService {
|
||||
}
|
||||
}
|
||||
String? _accessToken;
|
||||
final _log = Logger("ApiService");
|
||||
|
||||
setEndpoint(String endpoint) {
|
||||
_apiClient = ApiClient(basePath: endpoint);
|
||||
@@ -95,14 +97,17 @@ class ApiService {
|
||||
serverUrl += '/api';
|
||||
}
|
||||
|
||||
// Throw Socket or Timeout exceptions,
|
||||
// we do not care if the endpoints hits an HTTP error
|
||||
try {
|
||||
await client
|
||||
.get(
|
||||
Uri.parse(serverUrl),
|
||||
)
|
||||
final response = await client
|
||||
.get(Uri.parse("$serverUrl/server-info/ping"))
|
||||
.timeout(const Duration(seconds: 5));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_log.severe(
|
||||
"Server Gateway Error: ${response.body} - Cannot communicate to the server",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} on TimeoutException catch (_) {
|
||||
return false;
|
||||
} on SocketException catch (_) {
|
||||
|
||||
@@ -25,20 +25,30 @@ class SplashScreenPage extends HookConsumerWidget {
|
||||
void performLoggingIn() async {
|
||||
bool isSuccess = false;
|
||||
bool deviceIsOffline = false;
|
||||
|
||||
if (accessToken != null && serverUrl != null) {
|
||||
try {
|
||||
// Resolve API server endpoint from user provided serverUrl
|
||||
await apiService.resolveAndSetEndpoint(serverUrl);
|
||||
} on ApiException catch (e) {
|
||||
} on ApiException catch (error, stackTrace) {
|
||||
log.severe(
|
||||
"Failed to resolve endpoint [ApiException]",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
// okay, try to continue anyway if offline
|
||||
if (e.code == 503) {
|
||||
if (error.code == 503) {
|
||||
deviceIsOffline = true;
|
||||
log.fine("Device seems to be offline upon launch");
|
||||
log.warning("Device seems to be offline upon launch");
|
||||
} else {
|
||||
log.severe("Failed to resolve endpoint", e);
|
||||
log.severe("Failed to resolve endpoint", error);
|
||||
}
|
||||
} catch (e) {
|
||||
log.severe("Failed to resolve endpoint", e);
|
||||
} catch (error, stackTrace) {
|
||||
log.severe(
|
||||
"Failed to resolve endpoint [Catch All]",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -50,15 +60,11 @@ class SplashScreenPage extends HookConsumerWidget {
|
||||
offlineLogin: deviceIsOffline,
|
||||
);
|
||||
} catch (error, stackTrace) {
|
||||
ref.read(authenticationProvider.notifier).logout();
|
||||
|
||||
log.severe(
|
||||
'Cannot set success login info',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
|
||||
context.pushRoute(const LoginRoute());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +82,11 @@ class SplashScreenPage extends HookConsumerWidget {
|
||||
}
|
||||
context.replaceRoute(const TabControllerRoute());
|
||||
} else {
|
||||
log.severe(
|
||||
'Unable to login through offline or online methods - logging out completely',
|
||||
);
|
||||
|
||||
ref.read(authenticationProvider.notifier).logout();
|
||||
// User was unable to login through either offline or online methods
|
||||
context.replaceRoute(const LoginRoute());
|
||||
}
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ class CreateUserDto {
|
||||
|
||||
String password;
|
||||
|
||||
/// Minimum value: 1
|
||||
int? quotaSizeInBytes;
|
||||
|
||||
///
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ class DownloadInfoDto {
|
||||
///
|
||||
String? albumId;
|
||||
|
||||
/// Minimum value: 1
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ class JobSettingsDto {
|
||||
required this.concurrency,
|
||||
});
|
||||
|
||||
/// Minimum value: 1
|
||||
int concurrency;
|
||||
|
||||
@override
|
||||
|
||||
@@ -260,6 +260,7 @@ class MetadataSearchDto {
|
||||
///
|
||||
String? originalPath;
|
||||
|
||||
/// Minimum value: 1
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -286,6 +287,8 @@ class MetadataSearchDto {
|
||||
///
|
||||
String? resizePath;
|
||||
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 1000
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ class OnThisDayDto {
|
||||
required this.year,
|
||||
});
|
||||
|
||||
/// Minimum value: 1
|
||||
num year;
|
||||
|
||||
@override
|
||||
|
||||
@@ -23,10 +23,15 @@ class RecognitionConfig {
|
||||
|
||||
bool enabled;
|
||||
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 2
|
||||
double maxDistance;
|
||||
|
||||
/// Minimum value: 1
|
||||
int minFaces;
|
||||
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 1
|
||||
double minScore;
|
||||
|
||||
String modelName;
|
||||
|
||||
+3
@@ -192,6 +192,7 @@ class SmartSearchDto {
|
||||
///
|
||||
String? model;
|
||||
|
||||
/// Minimum value: 1
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
@@ -204,6 +205,8 @@ class SmartSearchDto {
|
||||
|
||||
String query;
|
||||
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 1000
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
||||
@@ -41,22 +41,30 @@ class SystemConfigFFmpegDto {
|
||||
|
||||
List<VideoCodec> acceptedVideoCodecs;
|
||||
|
||||
/// Minimum value: -1
|
||||
/// Maximum value: 16
|
||||
int bframes;
|
||||
|
||||
CQMode cqMode;
|
||||
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 51
|
||||
int crf;
|
||||
|
||||
/// Minimum value: 0
|
||||
int gopSize;
|
||||
|
||||
String maxBitrate;
|
||||
|
||||
/// Minimum value: 0
|
||||
int npl;
|
||||
|
||||
String preferredHwDevice;
|
||||
|
||||
String preset;
|
||||
|
||||
/// Minimum value: 0
|
||||
/// Maximum value: 6
|
||||
int refs;
|
||||
|
||||
AudioCodec targetAudioCodec;
|
||||
@@ -67,6 +75,7 @@ class SystemConfigFFmpegDto {
|
||||
|
||||
bool temporalAQ;
|
||||
|
||||
/// Minimum value: 0
|
||||
int threads;
|
||||
|
||||
ToneMapping tonemap;
|
||||
|
||||
@@ -28,12 +28,16 @@ class SystemConfigImageDto {
|
||||
|
||||
ImageFormat previewFormat;
|
||||
|
||||
/// Minimum value: 1
|
||||
int previewSize;
|
||||
|
||||
/// Minimum value: 1
|
||||
/// Maximum value: 100
|
||||
int quality;
|
||||
|
||||
ImageFormat thumbnailFormat;
|
||||
|
||||
/// Minimum value: 1
|
||||
int thumbnailSize;
|
||||
|
||||
@override
|
||||
|
||||
@@ -39,6 +39,7 @@ class SystemConfigOAuthDto {
|
||||
|
||||
String clientSecret;
|
||||
|
||||
/// Minimum value: 0
|
||||
num defaultStorageQuota;
|
||||
|
||||
bool enabled;
|
||||
|
||||
@@ -17,6 +17,7 @@ class SystemConfigTrashDto {
|
||||
required this.enabled,
|
||||
});
|
||||
|
||||
/// Minimum value: 0
|
||||
int days;
|
||||
|
||||
bool enabled;
|
||||
|
||||
@@ -16,6 +16,7 @@ class SystemConfigUserDto {
|
||||
required this.deleteDelay,
|
||||
});
|
||||
|
||||
/// Minimum value: 1
|
||||
int deleteDelay;
|
||||
|
||||
@override
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ class UpdateUserDto {
|
||||
///
|
||||
String? password;
|
||||
|
||||
/// Minimum value: 1
|
||||
int? quotaSizeInBytes;
|
||||
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user