Compare commits
5 Commits
v1.140.0
...
more-user-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3af54c6c9 | ||
|
|
f5954f4c9b | ||
|
|
147accd957 | ||
|
|
9487241481 | ||
|
|
460e1d4715 |
@@ -26,7 +26,7 @@ services:
|
||||
env_file: !reset []
|
||||
init:
|
||||
env_file: !reset []
|
||||
command: sh -c 'for path in /data /data/upload /usr/src/app/.pnpm-store /usr/src/app/server/node_modules /usr/src/app/server/dist /usr/src/app/.github/node_modules /usr/src/app/cli/node_modules /usr/src/app/docs/node_modules /usr/src/app/e2e/node_modules /usr/src/app/open-api/typescript-sdk/node_modules /usr/src/app/web/.svelte-kit /usr/src/app/web/coverage /usr/src/app/node_modules /usr/src/app/web/node_modules; do [ -e "$$path" ] && chown -R ${UID:-1000}:${GID:-1000} "$$path" || true; done'
|
||||
command: sh -c 'find /data -maxdepth 1 ! -path "/data/postgres" -type d -exec chown ${UID:-1000}:${GID:-1000} {} + 2>/dev/null || true; for path in /usr/src/app/.pnpm-store /usr/src/app/server/node_modules /usr/src/app/server/dist /usr/src/app/.github/node_modules /usr/src/app/cli/node_modules /usr/src/app/docs/node_modules /usr/src/app/e2e/node_modules /usr/src/app/open-api/typescript-sdk/node_modules /usr/src/app/web/.svelte-kit /usr/src/app/web/coverage /usr/src/app/node_modules /usr/src/app/web/node_modules; do [ -e "$$path" ] && chown -R ${UID:-1000}:${GID:-1000} "$$path" || true; done'
|
||||
immich-machine-learning:
|
||||
env_file: !reset []
|
||||
database:
|
||||
|
||||
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -569,7 +569,8 @@ jobs:
|
||||
- name: Build the app
|
||||
run: pnpm --filter immich build
|
||||
- name: Run API generation
|
||||
run: make open-api
|
||||
run: ./bin/generate-open-api.sh
|
||||
working-directory: open-api
|
||||
- name: Find file changes
|
||||
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
|
||||
id: verify-changed-files
|
||||
|
||||
38
Makefile
38
Makefile
@@ -60,20 +60,37 @@ VOLUME_DIRS = \
|
||||
./e2e/node_modules \
|
||||
./docs/node_modules \
|
||||
./server/node_modules \
|
||||
./server/dist \
|
||||
./open-api/typescript-sdk/node_modules \
|
||||
./.github/node_modules \
|
||||
./node_modules \
|
||||
./cli/node_modules
|
||||
|
||||
# create empty directories and chown to current user
|
||||
# Include .env file if it exists
|
||||
-include docker/.env
|
||||
|
||||
# Helper function to chown, on error suggest remediation and exit
|
||||
define safe_chown
|
||||
if chown $(2) $(or $(UID),1000):$(or $(GID),1000) "$(1)" 2>/dev/null; then \
|
||||
true; \
|
||||
else \
|
||||
echo "Permission denied when changing owner of volumes and upload location. Try running 'sudo make prepare-volumes' first."; \
|
||||
exit 1; \
|
||||
fi;
|
||||
endef
|
||||
# create empty directories and chown
|
||||
prepare-volumes:
|
||||
@for dir in $(VOLUME_DIRS); do \
|
||||
mkdir -p $$dir; \
|
||||
done
|
||||
@if [ -n "$(VOLUME_DIRS)" ]; then \
|
||||
chown -R $$(id -u):$$(id -g) $(VOLUME_DIRS); \
|
||||
fi
|
||||
@$(foreach dir,$(VOLUME_DIRS),mkdir -p $(dir);)
|
||||
@$(foreach dir,$(VOLUME_DIRS),$(call safe_chown,$(dir),-R))
|
||||
ifneq ($(UPLOAD_LOCATION),)
|
||||
ifeq ($(filter /%,$(UPLOAD_LOCATION)),)
|
||||
@mkdir -p "docker/$(UPLOAD_LOCATION)"
|
||||
@$(call safe_chown,docker/$(UPLOAD_LOCATION),)
|
||||
else
|
||||
@mkdir -p "$(UPLOAD_LOCATION)"
|
||||
@$(call safe_chown,$(UPLOAD_LOCATION),)
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
MODULES = e2e server web cli sdk docs .github
|
||||
|
||||
@@ -150,8 +167,9 @@ clean:
|
||||
find . -name ".svelte-kit" -type d -prune -exec rm -rf '{}' +
|
||||
find . -name "coverage" -type d -prune -exec rm -rf '{}' +
|
||||
find . -name ".pnpm-store" -type d -prune -exec rm -rf '{}' +
|
||||
command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml rm -v -f || true
|
||||
command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml rm -v -f || true
|
||||
command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml down -v --remove-orphans || true
|
||||
command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml down -v --remove-orphans || true
|
||||
|
||||
|
||||
setup-server-dev: install-server
|
||||
setup-web-dev: install-sdk build-sdk install-web
|
||||
|
||||
@@ -189,7 +189,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
user: 0:0
|
||||
command: sh -c 'for path in /usr/src/app/.pnpm-store /usr/src/app/server/node_modules /usr/src/app/server/dist /usr/src/app/.github/node_modules /usr/src/app/cli/node_modules /usr/src/app/docs/node_modules /usr/src/app/e2e/node_modules /usr/src/app/open-api/typescript-sdk/node_modules /usr/src/app/web/.svelte-kit /usr/src/app/web/coverage /usr/src/app/node_modules /usr/src/app/web/node_modules; do [ -e "$$path" ] && chown -R ${UID:-1000}:${GID:-1000} "$$path" || true; done'
|
||||
command: sh -c 'find /data -maxdepth 1 -type d -exec chown ${UID:-1000}:${GID:-1000} {} + 2>/dev/null || true; for path in /usr/src/app/.pnpm-store /usr/src/app/server/node_modules /usr/src/app/server/dist /usr/src/app/.github/node_modules /usr/src/app/cli/node_modules /usr/src/app/docs/node_modules /usr/src/app/e2e/node_modules /usr/src/app/open-api/typescript-sdk/node_modules /usr/src/app/web/.svelte-kit /usr/src/app/web/coverage /usr/src/app/node_modules /usr/src/app/web/node_modules; do [ -e "$$path" ] && chown -R ${UID:-1000}:${GID:-1000} "$$path" || true; done'
|
||||
volumes:
|
||||
- pnpm-store:/usr/src/app/.pnpm-store
|
||||
- server-node_modules:/usr/src/app/server/node_modules
|
||||
|
||||
@@ -33,7 +33,7 @@ Sometimes, an external library will not scan correctly. This can happen if Immic
|
||||
- Are the permissions set correctly?
|
||||
- Make sure you are using forward slashes (`/`) and not backward slashes.
|
||||
|
||||
To validate that Immich can reach your external library, start a shell inside the container. Run `docker exec -it immich_server bash` to a bash shell. If your import path is `/data/import/photos`, check it with `ls /data/import/photos`. Do the same check for the same in any microservices containers.
|
||||
To validate that Immich can reach your external library, start a shell inside the container. Run `docker exec -it immich_server bash` to a bash shell. If your import path is `/mnt/photos`, check it with `ls /mnt/photos`. If you are using a dedicated microservices container, make sure to add the same mount point and check for availability within the microservices container as well.
|
||||
|
||||
### Exclusion Patterns
|
||||
|
||||
|
||||
@@ -639,6 +639,8 @@
|
||||
"cannot_update_the_description": "Cannot update the description",
|
||||
"cast": "Cast",
|
||||
"cast_description": "Configure available cast destinations",
|
||||
"cellular_data_for_photos": "Cellular data for photos",
|
||||
"cellular_data_for_videos": "Cellular data for videos",
|
||||
"change_date": "Change date",
|
||||
"change_description": "Change description",
|
||||
"change_display_order": "Change display order",
|
||||
|
||||
@@ -65,7 +65,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
||||
pnpm install --frozen-lockfile --prefix server
|
||||
pnpm --prefix server run build
|
||||
|
||||
make open-api
|
||||
( cd ./open-api && bash ./bin/generate-open-api.sh )
|
||||
|
||||
jq --arg version "$NEXT_SERVER" '.version = $version' open-api/typescript-sdk/package.json > open-api/typescript-sdk/package.json.tmp && mv open-api/typescript-sdk/package.json.tmp open-api/typescript-sdk/package.json
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -129,6 +129,8 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = Sync;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -505,14 +507,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
@@ -541,14 +539,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
|
||||
@@ -75,8 +75,8 @@ enum StoreKey<T> {
|
||||
betaPromptShown<bool>._(1001),
|
||||
betaTimeline<bool>._(1002),
|
||||
enableBackup<bool>._(1003),
|
||||
useWifiForUploadVideos<bool>._(1004),
|
||||
useWifiForUploadPhotos<bool>._(1005);
|
||||
useCellularForUploadVideos<bool>._(1004),
|
||||
useCellularForUploadPhotos<bool>._(1005);
|
||||
|
||||
const StoreKey._(this.id);
|
||||
final int id;
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
@@ -93,7 +95,11 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
const _BackupCard(),
|
||||
const _RemainderCard(),
|
||||
const Divider(),
|
||||
const SizedBox(height: 4),
|
||||
const _CellularBackupStatus(),
|
||||
const SizedBox(height: 4),
|
||||
BackupToggleButton(onStart: () async => await startBackup(), onStop: () async => await stopBackup()),
|
||||
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.info_outline_rounded),
|
||||
onPressed: () => context.pushRoute(const DriftUploadDetailRoute()),
|
||||
@@ -109,6 +115,64 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class _CellularBackupStatus extends ConsumerWidget {
|
||||
const _CellularBackupStatus();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final cellularReqForVideos = Store.watch(StoreKey.useCellularForUploadVideos);
|
||||
final cellularReqForPhotos = Store.watch(StoreKey.useCellularForUploadPhotos);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(const DriftBackupOptionsRoute()),
|
||||
child: Row(
|
||||
children: [
|
||||
StreamBuilder(
|
||||
stream: cellularReqForVideos,
|
||||
initialData: Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false,
|
||||
builder: (context, snapshot) {
|
||||
return Expanded(
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
leading: Icon(
|
||||
snapshot.data ?? false ? Icons.check_circle : Icons.cancel_outlined,
|
||||
size: 16,
|
||||
color: snapshot.data ?? false ? Colors.green : context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
title: Text(
|
||||
"cellular_data_for_videos".t(context: context),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: cellularReqForPhotos,
|
||||
initialData: Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false,
|
||||
builder: (context, snapshot) {
|
||||
return Expanded(
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
leading: Icon(
|
||||
snapshot.data ?? false ? Icons.check_circle : Icons.cancel_outlined,
|
||||
size: 16,
|
||||
color: snapshot.data ?? false ? Colors.green : context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
title: Text(
|
||||
"cellular_data_for_photos".t(context: context),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
const _BackupAlbumSelectionCard();
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
bool hasPopped = false;
|
||||
final previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
||||
final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
||||
final previousWifiReqForVideos = Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false;
|
||||
final previousWifiReqForPhotos = Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false;
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
// There is an issue with Flutter where the pop event
|
||||
// can be triggered multiple times, so we guard it with _hasPopped
|
||||
|
||||
final currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
||||
final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
||||
final currentWifiReqForVideos = Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false;
|
||||
final currentWifiReqForPhotos = Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false;
|
||||
|
||||
if (currentWifiReqForVideos == previousWifiReqForVideos &&
|
||||
currentWifiReqForPhotos == previousWifiReqForPhotos) {
|
||||
|
||||
@@ -48,8 +48,8 @@ enum AppSettingsEnum<T> {
|
||||
photoManagerCustomFilter<bool>(StoreKey.photoManagerCustomFilter, null, true),
|
||||
betaTimeline<bool>(StoreKey.betaTimeline, null, false),
|
||||
enableBackup<bool>(StoreKey.enableBackup, null, false),
|
||||
useCellularForUploadVideos<bool>(StoreKey.useWifiForUploadVideos, null, false),
|
||||
useCellularForUploadPhotos<bool>(StoreKey.useWifiForUploadPhotos, null, false),
|
||||
useCellularForUploadVideos<bool>(StoreKey.useCellularForUploadVideos, null, false),
|
||||
useCellularForUploadPhotos<bool>(StoreKey.useCellularForUploadPhotos, null, false),
|
||||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false);
|
||||
|
||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||
|
||||
@@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
final uploadServiceProvider = Provider((ref) {
|
||||
@@ -57,6 +58,7 @@ class UploadService {
|
||||
|
||||
Stream<TaskStatusUpdate> get taskStatusStream => _taskStatusController.stream;
|
||||
Stream<TaskProgressUpdate> get taskProgressStream => _taskProgressController.stream;
|
||||
final Logger _log = Logger('UploadService');
|
||||
|
||||
bool shouldAbortQueuingTasks = false;
|
||||
|
||||
@@ -127,11 +129,16 @@ class UploadService {
|
||||
|
||||
final candidates = await _backupRepository.getCandidates(userId);
|
||||
if (candidates.isEmpty) {
|
||||
_log.info("No backup candidates found for user $userId");
|
||||
return;
|
||||
}
|
||||
|
||||
_log.info("Starting backup for ${candidates.length} candidates");
|
||||
onEnqueueTasks(EnqueueStatus(enqueueCount: 0, totalCount: candidates.length));
|
||||
|
||||
const batchSize = 100;
|
||||
int count = 0;
|
||||
int skippedAssets = 0;
|
||||
for (int i = 0; i < candidates.length; i += batchSize) {
|
||||
if (shouldAbortQueuingTasks) {
|
||||
break;
|
||||
@@ -144,16 +151,22 @@ class UploadService {
|
||||
final task = await _getUploadTask(asset);
|
||||
if (task != null) {
|
||||
tasks.add(task);
|
||||
} else {
|
||||
skippedAssets++;
|
||||
_log.warning("Skipped asset ${asset.id} (${asset.name}) - unable to create upload task");
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.isNotEmpty && !shouldAbortQueuingTasks) {
|
||||
count += tasks.length;
|
||||
await enqueueTasks(tasks);
|
||||
count += tasks.length;
|
||||
onEnqueueTasks(EnqueueStatus(enqueueCount: count, totalCount: candidates.length));
|
||||
|
||||
onEnqueueTasks(EnqueueStatus(enqueueCount: count, totalCount: candidates.length));
|
||||
if (tasks.isNotEmpty && !shouldAbortQueuingTasks) {
|
||||
_log.info("Enqueuing ${tasks.length} upload tasks");
|
||||
await enqueueTasks(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
_log.info("Upload queueing completed: $count tasks enqueued, $skippedAssets assets skipped");
|
||||
}
|
||||
|
||||
// Enqueue All does not work from the background on Android yet. This method is a temporary workaround
|
||||
@@ -165,9 +178,14 @@ class UploadService {
|
||||
|
||||
final candidates = await _backupRepository.getCandidates(userId);
|
||||
if (candidates.isEmpty) {
|
||||
debugPrint("No backup candidates found for serial backup");
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint("Starting serial backup for ${candidates.length} candidates");
|
||||
int skippedAssets = 0;
|
||||
int enqueuedTasks = 0;
|
||||
|
||||
for (final asset in candidates) {
|
||||
if (shouldAbortQueuingTasks) {
|
||||
break;
|
||||
@@ -176,8 +194,13 @@ class UploadService {
|
||||
final task = await _getUploadTask(asset);
|
||||
if (task != null) {
|
||||
await _uploadRepository.enqueueBackground(task);
|
||||
enqueuedTasks++;
|
||||
} else {
|
||||
skippedAssets++;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint("Serial backup completed: $enqueuedTasks tasks enqueued, $skippedAssets assets skipped");
|
||||
}
|
||||
|
||||
/// Cancel all ongoing uploads and reset the upload queue
|
||||
@@ -245,6 +268,7 @@ class UploadService {
|
||||
Future<UploadTask?> _getUploadTask(LocalAsset asset, {String group = kBackupGroup, int? priority}) async {
|
||||
final entity = await _storageRepository.getAssetEntityForAsset(asset);
|
||||
if (entity == null) {
|
||||
_log.warning("Cannot get AssetEntity for asset ${asset.id} (${asset.name}) created on ${asset.createdAt}");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -267,6 +291,9 @@ class UploadService {
|
||||
}
|
||||
|
||||
if (file == null) {
|
||||
_log.warning(
|
||||
"Cannot get file for asset ${asset.id} (${asset.name}) created on ${asset.createdAt} - file may have been deleted or moved",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class _UseWifiForUploadVideosButton extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final valueStream = Store.watch(StoreKey.useWifiForUploadVideos);
|
||||
final valueStream = Store.watch(StoreKey.useCellularForUploadVideos);
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
@@ -32,7 +32,7 @@ class _UseWifiForUploadVideosButton extends ConsumerWidget {
|
||||
subtitle: Text("network_requirement_videos_upload".t(context: context), style: context.textTheme.labelLarge),
|
||||
trailing: StreamBuilder(
|
||||
stream: valueStream,
|
||||
initialData: Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false,
|
||||
initialData: Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false,
|
||||
builder: (context, snapshot) {
|
||||
final value = snapshot.data ?? false;
|
||||
return Switch(
|
||||
@@ -54,7 +54,7 @@ class _UseWifiForUploadPhotosButton extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final valueStream = Store.watch(StoreKey.useWifiForUploadPhotos);
|
||||
final valueStream = Store.watch(StoreKey.useCellularForUploadPhotos);
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
@@ -64,7 +64,7 @@ class _UseWifiForUploadPhotosButton extends ConsumerWidget {
|
||||
subtitle: Text("network_requirement_photos_upload".t(context: context), style: context.textTheme.labelLarge),
|
||||
trailing: StreamBuilder(
|
||||
stream: valueStream,
|
||||
initialData: Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false,
|
||||
initialData: Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false,
|
||||
builder: (context, snapshot) {
|
||||
final value = snapshot.data ?? false;
|
||||
return Switch(
|
||||
|
||||
@@ -468,9 +468,8 @@ where
|
||||
"asset"."visibility" != $1
|
||||
and "asset"."deletedAt" is null
|
||||
and "job_status"."previewAt" is not null
|
||||
and "job_status"."facesRecognizedAt" is null
|
||||
order by
|
||||
"asset"."createdAt" desc
|
||||
"asset"."fileCreatedAt" desc
|
||||
|
||||
-- AssetJobRepository.streamForMigrationJob
|
||||
select
|
||||
|
||||
@@ -12,6 +12,8 @@ where
|
||||
and "fileCreatedAt" is not null
|
||||
and "fileModifiedAt" is not null
|
||||
and "localDateTime" is not null
|
||||
order by
|
||||
"directoryPath" asc
|
||||
|
||||
-- ViewRepository.getAssetsByOriginalPath
|
||||
select
|
||||
|
||||
@@ -334,9 +334,9 @@ export class AssetJobRepository {
|
||||
@GenerateSql({ params: [], stream: true })
|
||||
streamForDetectFacesJob(force?: boolean) {
|
||||
return this.assetsWithPreviews()
|
||||
.$if(!force, (qb) => qb.where('job_status.facesRecognizedAt', 'is', null))
|
||||
.$if(force === false, (qb) => qb.where('job_status.facesRecognizedAt', 'is', null))
|
||||
.select(['asset.id'])
|
||||
.orderBy('asset.createdAt', 'desc')
|
||||
.orderBy('asset.fileCreatedAt', 'desc')
|
||||
.stream();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export class ViewRepository {
|
||||
.where('fileCreatedAt', 'is not', null)
|
||||
.where('fileModifiedAt', 'is not', null)
|
||||
.where('localDateTime', 'is not', null)
|
||||
.orderBy('directoryPath', 'asc')
|
||||
.execute();
|
||||
|
||||
return results.map((row) => row.directoryPath.replaceAll(/\/$/g, ''));
|
||||
|
||||
Reference in New Issue
Block a user