mirror of
https://github.com/immich-app/immich.git
synced 2025-12-17 09:13:17 +03:00
Compare commits
6 Commits
bring-back
...
fix/period
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8e6080ddc | ||
|
|
70300ad3d4 | ||
|
|
91fe3c8d96 | ||
|
|
3b6e23fed7 | ||
|
|
d37cae5dcd | ||
|
|
94044c98bf |
1
mobile/drift_schemas/main/drift_schema_v13.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v13.json
generated
Normal file
File diff suppressed because one or more lines are too long
@@ -7,3 +7,5 @@ enum AssetVisibilityEnum { timeline, hidden, archive, locked }
|
|||||||
enum SortUserBy { id }
|
enum SortUserBy { id }
|
||||||
|
|
||||||
enum ActionSource { timeline, viewer }
|
enum ActionSource { timeline, viewer }
|
||||||
|
|
||||||
|
enum UploadErrorType { none, network, client, server, unknown }
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class LocalAssetUploadEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const LocalAssetUploadEntity();
|
||||||
|
|
||||||
|
TextColumn get assetId => text().references(LocalAssetEntity, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
IntColumn get numberOfAttempts => integer().withDefault(const Constant(0))();
|
||||||
|
DateTimeColumn get lastAttemptAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
IntColumn get errorType => intEnum<UploadErrorType>().withDefault(const Constant(0))();
|
||||||
|
TextColumn get errorMessage => text().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {assetId};
|
||||||
|
}
|
||||||
783
mobile/lib/infrastructure/entities/local_asset_upload_entity.drift.dart
generated
Normal file
783
mobile/lib/infrastructure/entities/local_asset_upload_entity.drift.dart
generated
Normal file
@@ -0,0 +1,783 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset_upload_entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/constants/enums.dart' as i2;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset_upload_entity.dart'
|
||||||
|
as i3;
|
||||||
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'
|
||||||
|
as i5;
|
||||||
|
import 'package:drift/internal/modular.dart' as i6;
|
||||||
|
|
||||||
|
typedef $$LocalAssetUploadEntityTableCreateCompanionBuilder =
|
||||||
|
i1.LocalAssetUploadEntityCompanion Function({
|
||||||
|
required String assetId,
|
||||||
|
i0.Value<int> numberOfAttempts,
|
||||||
|
i0.Value<DateTime> lastAttemptAt,
|
||||||
|
i0.Value<i2.UploadErrorType> errorType,
|
||||||
|
i0.Value<String?> errorMessage,
|
||||||
|
});
|
||||||
|
typedef $$LocalAssetUploadEntityTableUpdateCompanionBuilder =
|
||||||
|
i1.LocalAssetUploadEntityCompanion Function({
|
||||||
|
i0.Value<String> assetId,
|
||||||
|
i0.Value<int> numberOfAttempts,
|
||||||
|
i0.Value<DateTime> lastAttemptAt,
|
||||||
|
i0.Value<i2.UploadErrorType> errorType,
|
||||||
|
i0.Value<String?> errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final class $$LocalAssetUploadEntityTableReferences
|
||||||
|
extends
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAssetUploadEntityTable,
|
||||||
|
i1.LocalAssetUploadEntityData
|
||||||
|
> {
|
||||||
|
$$LocalAssetUploadEntityTableReferences(
|
||||||
|
super.$_db,
|
||||||
|
super.$_table,
|
||||||
|
super.$_typedResult,
|
||||||
|
);
|
||||||
|
|
||||||
|
static i5.$LocalAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
|
||||||
|
i6.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i5.$LocalAssetEntityTable>('local_asset_entity')
|
||||||
|
.createAlias(
|
||||||
|
i0.$_aliasNameGenerator(
|
||||||
|
i6.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i1.$LocalAssetUploadEntityTable>(
|
||||||
|
'local_asset_upload_entity',
|
||||||
|
)
|
||||||
|
.assetId,
|
||||||
|
i6.ReadDatabaseContainer(
|
||||||
|
db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity').id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
i5.$$LocalAssetEntityTableProcessedTableManager get assetId {
|
||||||
|
final $_column = $_itemColumn<String>('asset_id')!;
|
||||||
|
|
||||||
|
final manager = i5
|
||||||
|
.$$LocalAssetEntityTableTableManager(
|
||||||
|
$_db,
|
||||||
|
i6.ReadDatabaseContainer(
|
||||||
|
$_db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
)
|
||||||
|
.filter((f) => f.id.sqlEquals($_column));
|
||||||
|
final item = $_typedResult.readTableOrNull(_assetIdTable($_db));
|
||||||
|
if (item == null) return manager;
|
||||||
|
return i0.ProcessedTableManager(
|
||||||
|
manager.$state.copyWith(prefetchedData: [item]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAssetUploadEntityTableFilterComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAssetUploadEntityTable> {
|
||||||
|
$$LocalAssetUploadEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<int> get numberOfAttempts => $composableBuilder(
|
||||||
|
column: $table.numberOfAttempts,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get lastAttemptAt => $composableBuilder(
|
||||||
|
column: $table.lastAttemptAt,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnWithTypeConverterFilters<i2.UploadErrorType, i2.UploadErrorType, int>
|
||||||
|
get errorType => $composableBuilder(
|
||||||
|
column: $table.errorType,
|
||||||
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get errorMessage => $composableBuilder(
|
||||||
|
column: $table.errorMessage,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i5.$$LocalAssetEntityTableFilterComposer get assetId {
|
||||||
|
final i5.$$LocalAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder:
|
||||||
|
(
|
||||||
|
joinBuilder, {
|
||||||
|
$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
}) => i5.$$LocalAssetEntityTableFilterComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i6.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAssetUploadEntityTableOrderingComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAssetUploadEntityTable> {
|
||||||
|
$$LocalAssetUploadEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<int> get numberOfAttempts => $composableBuilder(
|
||||||
|
column: $table.numberOfAttempts,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get lastAttemptAt => $composableBuilder(
|
||||||
|
column: $table.lastAttemptAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<int> get errorType => $composableBuilder(
|
||||||
|
column: $table.errorType,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get errorMessage => $composableBuilder(
|
||||||
|
column: $table.errorMessage,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i5.$$LocalAssetEntityTableOrderingComposer get assetId {
|
||||||
|
final i5.$$LocalAssetEntityTableOrderingComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder:
|
||||||
|
(
|
||||||
|
joinBuilder, {
|
||||||
|
$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
}) => i5.$$LocalAssetEntityTableOrderingComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i6.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAssetUploadEntityTableAnnotationComposer
|
||||||
|
extends i0.Composer<i0.GeneratedDatabase, i1.$LocalAssetUploadEntityTable> {
|
||||||
|
$$LocalAssetUploadEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<int> get numberOfAttempts => $composableBuilder(
|
||||||
|
column: $table.numberOfAttempts,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get lastAttemptAt => $composableBuilder(
|
||||||
|
column: $table.lastAttemptAt,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.GeneratedColumnWithTypeConverter<i2.UploadErrorType, int> get errorType =>
|
||||||
|
$composableBuilder(column: $table.errorType, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get errorMessage => $composableBuilder(
|
||||||
|
column: $table.errorMessage,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
|
i5.$$LocalAssetEntityTableAnnotationComposer get assetId {
|
||||||
|
final i5.$$LocalAssetEntityTableAnnotationComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder:
|
||||||
|
(
|
||||||
|
joinBuilder, {
|
||||||
|
$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
}) => i5.$$LocalAssetEntityTableAnnotationComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i6.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i5.$LocalAssetEntityTable>('local_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$LocalAssetUploadEntityTableTableManager
|
||||||
|
extends
|
||||||
|
i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAssetUploadEntityTable,
|
||||||
|
i1.LocalAssetUploadEntityData,
|
||||||
|
i1.$$LocalAssetUploadEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAssetUploadEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAssetUploadEntityTableAnnotationComposer,
|
||||||
|
$$LocalAssetUploadEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAssetUploadEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.LocalAssetUploadEntityData,
|
||||||
|
i1.$$LocalAssetUploadEntityTableReferences,
|
||||||
|
),
|
||||||
|
i1.LocalAssetUploadEntityData,
|
||||||
|
i0.PrefetchHooks Function({bool assetId})
|
||||||
|
> {
|
||||||
|
$$LocalAssetUploadEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db,
|
||||||
|
i1.$LocalAssetUploadEntityTable table,
|
||||||
|
) : super(
|
||||||
|
i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$LocalAssetUploadEntityTableFilterComposer(
|
||||||
|
$db: db,
|
||||||
|
$table: table,
|
||||||
|
),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
i1.$$LocalAssetUploadEntityTableOrderingComposer(
|
||||||
|
$db: db,
|
||||||
|
$table: table,
|
||||||
|
),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$LocalAssetUploadEntityTableAnnotationComposer(
|
||||||
|
$db: db,
|
||||||
|
$table: table,
|
||||||
|
),
|
||||||
|
updateCompanionCallback:
|
||||||
|
({
|
||||||
|
i0.Value<String> assetId = const i0.Value.absent(),
|
||||||
|
i0.Value<int> numberOfAttempts = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> lastAttemptAt = const i0.Value.absent(),
|
||||||
|
i0.Value<i2.UploadErrorType> errorType =
|
||||||
|
const i0.Value.absent(),
|
||||||
|
i0.Value<String?> errorMessage = const i0.Value.absent(),
|
||||||
|
}) => i1.LocalAssetUploadEntityCompanion(
|
||||||
|
assetId: assetId,
|
||||||
|
numberOfAttempts: numberOfAttempts,
|
||||||
|
lastAttemptAt: lastAttemptAt,
|
||||||
|
errorType: errorType,
|
||||||
|
errorMessage: errorMessage,
|
||||||
|
),
|
||||||
|
createCompanionCallback:
|
||||||
|
({
|
||||||
|
required String assetId,
|
||||||
|
i0.Value<int> numberOfAttempts = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime> lastAttemptAt = const i0.Value.absent(),
|
||||||
|
i0.Value<i2.UploadErrorType> errorType =
|
||||||
|
const i0.Value.absent(),
|
||||||
|
i0.Value<String?> errorMessage = const i0.Value.absent(),
|
||||||
|
}) => i1.LocalAssetUploadEntityCompanion.insert(
|
||||||
|
assetId: assetId,
|
||||||
|
numberOfAttempts: numberOfAttempts,
|
||||||
|
lastAttemptAt: lastAttemptAt,
|
||||||
|
errorType: errorType,
|
||||||
|
errorMessage: errorMessage,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map(
|
||||||
|
(e) => (
|
||||||
|
e.readTable(table),
|
||||||
|
i1.$$LocalAssetUploadEntityTableReferences(db, table, e),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: ({assetId = false}) {
|
||||||
|
return i0.PrefetchHooks(
|
||||||
|
db: db,
|
||||||
|
explicitlyWatchedTables: [],
|
||||||
|
addJoins:
|
||||||
|
<
|
||||||
|
T extends i0.TableManagerState<
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic
|
||||||
|
>
|
||||||
|
>(state) {
|
||||||
|
if (assetId) {
|
||||||
|
state =
|
||||||
|
state.withJoin(
|
||||||
|
currentTable: table,
|
||||||
|
currentColumn: table.assetId,
|
||||||
|
referencedTable: i1
|
||||||
|
.$$LocalAssetUploadEntityTableReferences
|
||||||
|
._assetIdTable(db),
|
||||||
|
referencedColumn: i1
|
||||||
|
.$$LocalAssetUploadEntityTableReferences
|
||||||
|
._assetIdTable(db)
|
||||||
|
.id,
|
||||||
|
)
|
||||||
|
as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
getPrefetchedDataCallback: (items) async {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$LocalAssetUploadEntityTableProcessedTableManager =
|
||||||
|
i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$LocalAssetUploadEntityTable,
|
||||||
|
i1.LocalAssetUploadEntityData,
|
||||||
|
i1.$$LocalAssetUploadEntityTableFilterComposer,
|
||||||
|
i1.$$LocalAssetUploadEntityTableOrderingComposer,
|
||||||
|
i1.$$LocalAssetUploadEntityTableAnnotationComposer,
|
||||||
|
$$LocalAssetUploadEntityTableCreateCompanionBuilder,
|
||||||
|
$$LocalAssetUploadEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.LocalAssetUploadEntityData,
|
||||||
|
i1.$$LocalAssetUploadEntityTableReferences,
|
||||||
|
),
|
||||||
|
i1.LocalAssetUploadEntityData,
|
||||||
|
i0.PrefetchHooks Function({bool assetId})
|
||||||
|
>;
|
||||||
|
|
||||||
|
class $LocalAssetUploadEntityTable extends i3.LocalAssetUploadEntity
|
||||||
|
with
|
||||||
|
i0.TableInfo<
|
||||||
|
$LocalAssetUploadEntityTable,
|
||||||
|
i1.LocalAssetUploadEntityData
|
||||||
|
> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$LocalAssetUploadEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta(
|
||||||
|
'assetId',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> assetId = i0.GeneratedColumn<String>(
|
||||||
|
'asset_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES local_asset_entity (id) ON DELETE CASCADE',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _numberOfAttemptsMeta =
|
||||||
|
const i0.VerificationMeta('numberOfAttempts');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<int> numberOfAttempts = i0.GeneratedColumn<int>(
|
||||||
|
'number_of_attempts',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const i4.Constant(0),
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _lastAttemptAtMeta =
|
||||||
|
const i0.VerificationMeta('lastAttemptAt');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> lastAttemptAt =
|
||||||
|
i0.GeneratedColumn<DateTime>(
|
||||||
|
'last_attempt_at',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: i4.currentDateAndTime,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumnWithTypeConverter<i2.UploadErrorType, int>
|
||||||
|
errorType =
|
||||||
|
i0.GeneratedColumn<int>(
|
||||||
|
'error_type',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const i4.Constant(0),
|
||||||
|
).withConverter<i2.UploadErrorType>(
|
||||||
|
i1.$LocalAssetUploadEntityTable.$convertererrorType,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _errorMessageMeta =
|
||||||
|
const i0.VerificationMeta('errorMessage');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> errorMessage =
|
||||||
|
i0.GeneratedColumn<String>(
|
||||||
|
'error_message',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
|
assetId,
|
||||||
|
numberOfAttempts,
|
||||||
|
lastAttemptAt,
|
||||||
|
errorType,
|
||||||
|
errorMessage,
|
||||||
|
];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'local_asset_upload_entity';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.LocalAssetUploadEntityData> instance, {
|
||||||
|
bool isInserting = false,
|
||||||
|
}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('asset_id')) {
|
||||||
|
context.handle(
|
||||||
|
_assetIdMeta,
|
||||||
|
assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_assetIdMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('number_of_attempts')) {
|
||||||
|
context.handle(
|
||||||
|
_numberOfAttemptsMeta,
|
||||||
|
numberOfAttempts.isAcceptableOrUnknown(
|
||||||
|
data['number_of_attempts']!,
|
||||||
|
_numberOfAttemptsMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('last_attempt_at')) {
|
||||||
|
context.handle(
|
||||||
|
_lastAttemptAtMeta,
|
||||||
|
lastAttemptAt.isAcceptableOrUnknown(
|
||||||
|
data['last_attempt_at']!,
|
||||||
|
_lastAttemptAtMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('error_message')) {
|
||||||
|
context.handle(
|
||||||
|
_errorMessageMeta,
|
||||||
|
errorMessage.isAcceptableOrUnknown(
|
||||||
|
data['error_message']!,
|
||||||
|
_errorMessageMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {assetId};
|
||||||
|
@override
|
||||||
|
i1.LocalAssetUploadEntityData map(
|
||||||
|
Map<String, dynamic> data, {
|
||||||
|
String? tablePrefix,
|
||||||
|
}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.LocalAssetUploadEntityData(
|
||||||
|
assetId: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}asset_id'],
|
||||||
|
)!,
|
||||||
|
numberOfAttempts: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}number_of_attempts'],
|
||||||
|
)!,
|
||||||
|
lastAttemptAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}last_attempt_at'],
|
||||||
|
)!,
|
||||||
|
errorType: i1.$LocalAssetUploadEntityTable.$convertererrorType.fromSql(
|
||||||
|
attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}error_type'],
|
||||||
|
)!,
|
||||||
|
),
|
||||||
|
errorMessage: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}error_message'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$LocalAssetUploadEntityTable createAlias(String alias) {
|
||||||
|
return $LocalAssetUploadEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
static i0.JsonTypeConverter2<i2.UploadErrorType, int, int>
|
||||||
|
$convertererrorType = const i0.EnumIndexConverter<i2.UploadErrorType>(
|
||||||
|
i2.UploadErrorType.values,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAssetUploadEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.LocalAssetUploadEntityData> {
|
||||||
|
final String assetId;
|
||||||
|
final int numberOfAttempts;
|
||||||
|
final DateTime lastAttemptAt;
|
||||||
|
final i2.UploadErrorType errorType;
|
||||||
|
final String? errorMessage;
|
||||||
|
const LocalAssetUploadEntityData({
|
||||||
|
required this.assetId,
|
||||||
|
required this.numberOfAttempts,
|
||||||
|
required this.lastAttemptAt,
|
||||||
|
required this.errorType,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['asset_id'] = i0.Variable<String>(assetId);
|
||||||
|
map['number_of_attempts'] = i0.Variable<int>(numberOfAttempts);
|
||||||
|
map['last_attempt_at'] = i0.Variable<DateTime>(lastAttemptAt);
|
||||||
|
{
|
||||||
|
map['error_type'] = i0.Variable<int>(
|
||||||
|
i1.$LocalAssetUploadEntityTable.$convertererrorType.toSql(errorType),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || errorMessage != null) {
|
||||||
|
map['error_message'] = i0.Variable<String>(errorMessage);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory LocalAssetUploadEntityData.fromJson(
|
||||||
|
Map<String, dynamic> json, {
|
||||||
|
i0.ValueSerializer? serializer,
|
||||||
|
}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return LocalAssetUploadEntityData(
|
||||||
|
assetId: serializer.fromJson<String>(json['assetId']),
|
||||||
|
numberOfAttempts: serializer.fromJson<int>(json['numberOfAttempts']),
|
||||||
|
lastAttemptAt: serializer.fromJson<DateTime>(json['lastAttemptAt']),
|
||||||
|
errorType: i1.$LocalAssetUploadEntityTable.$convertererrorType.fromJson(
|
||||||
|
serializer.fromJson<int>(json['errorType']),
|
||||||
|
),
|
||||||
|
errorMessage: serializer.fromJson<String?>(json['errorMessage']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'assetId': serializer.toJson<String>(assetId),
|
||||||
|
'numberOfAttempts': serializer.toJson<int>(numberOfAttempts),
|
||||||
|
'lastAttemptAt': serializer.toJson<DateTime>(lastAttemptAt),
|
||||||
|
'errorType': serializer.toJson<int>(
|
||||||
|
i1.$LocalAssetUploadEntityTable.$convertererrorType.toJson(errorType),
|
||||||
|
),
|
||||||
|
'errorMessage': serializer.toJson<String?>(errorMessage),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAssetUploadEntityData copyWith({
|
||||||
|
String? assetId,
|
||||||
|
int? numberOfAttempts,
|
||||||
|
DateTime? lastAttemptAt,
|
||||||
|
i2.UploadErrorType? errorType,
|
||||||
|
i0.Value<String?> errorMessage = const i0.Value.absent(),
|
||||||
|
}) => i1.LocalAssetUploadEntityData(
|
||||||
|
assetId: assetId ?? this.assetId,
|
||||||
|
numberOfAttempts: numberOfAttempts ?? this.numberOfAttempts,
|
||||||
|
lastAttemptAt: lastAttemptAt ?? this.lastAttemptAt,
|
||||||
|
errorType: errorType ?? this.errorType,
|
||||||
|
errorMessage: errorMessage.present ? errorMessage.value : this.errorMessage,
|
||||||
|
);
|
||||||
|
LocalAssetUploadEntityData copyWithCompanion(
|
||||||
|
i1.LocalAssetUploadEntityCompanion data,
|
||||||
|
) {
|
||||||
|
return LocalAssetUploadEntityData(
|
||||||
|
assetId: data.assetId.present ? data.assetId.value : this.assetId,
|
||||||
|
numberOfAttempts: data.numberOfAttempts.present
|
||||||
|
? data.numberOfAttempts.value
|
||||||
|
: this.numberOfAttempts,
|
||||||
|
lastAttemptAt: data.lastAttemptAt.present
|
||||||
|
? data.lastAttemptAt.value
|
||||||
|
: this.lastAttemptAt,
|
||||||
|
errorType: data.errorType.present ? data.errorType.value : this.errorType,
|
||||||
|
errorMessage: data.errorMessage.present
|
||||||
|
? data.errorMessage.value
|
||||||
|
: this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAssetUploadEntityData(')
|
||||||
|
..write('assetId: $assetId, ')
|
||||||
|
..write('numberOfAttempts: $numberOfAttempts, ')
|
||||||
|
..write('lastAttemptAt: $lastAttemptAt, ')
|
||||||
|
..write('errorType: $errorType, ')
|
||||||
|
..write('errorMessage: $errorMessage')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
assetId,
|
||||||
|
numberOfAttempts,
|
||||||
|
lastAttemptAt,
|
||||||
|
errorType,
|
||||||
|
errorMessage,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.LocalAssetUploadEntityData &&
|
||||||
|
other.assetId == this.assetId &&
|
||||||
|
other.numberOfAttempts == this.numberOfAttempts &&
|
||||||
|
other.lastAttemptAt == this.lastAttemptAt &&
|
||||||
|
other.errorType == this.errorType &&
|
||||||
|
other.errorMessage == this.errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalAssetUploadEntityCompanion
|
||||||
|
extends i0.UpdateCompanion<i1.LocalAssetUploadEntityData> {
|
||||||
|
final i0.Value<String> assetId;
|
||||||
|
final i0.Value<int> numberOfAttempts;
|
||||||
|
final i0.Value<DateTime> lastAttemptAt;
|
||||||
|
final i0.Value<i2.UploadErrorType> errorType;
|
||||||
|
final i0.Value<String?> errorMessage;
|
||||||
|
const LocalAssetUploadEntityCompanion({
|
||||||
|
this.assetId = const i0.Value.absent(),
|
||||||
|
this.numberOfAttempts = const i0.Value.absent(),
|
||||||
|
this.lastAttemptAt = const i0.Value.absent(),
|
||||||
|
this.errorType = const i0.Value.absent(),
|
||||||
|
this.errorMessage = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
LocalAssetUploadEntityCompanion.insert({
|
||||||
|
required String assetId,
|
||||||
|
this.numberOfAttempts = const i0.Value.absent(),
|
||||||
|
this.lastAttemptAt = const i0.Value.absent(),
|
||||||
|
this.errorType = const i0.Value.absent(),
|
||||||
|
this.errorMessage = const i0.Value.absent(),
|
||||||
|
}) : assetId = i0.Value(assetId);
|
||||||
|
static i0.Insertable<i1.LocalAssetUploadEntityData> custom({
|
||||||
|
i0.Expression<String>? assetId,
|
||||||
|
i0.Expression<int>? numberOfAttempts,
|
||||||
|
i0.Expression<DateTime>? lastAttemptAt,
|
||||||
|
i0.Expression<int>? errorType,
|
||||||
|
i0.Expression<String>? errorMessage,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (assetId != null) 'asset_id': assetId,
|
||||||
|
if (numberOfAttempts != null) 'number_of_attempts': numberOfAttempts,
|
||||||
|
if (lastAttemptAt != null) 'last_attempt_at': lastAttemptAt,
|
||||||
|
if (errorType != null) 'error_type': errorType,
|
||||||
|
if (errorMessage != null) 'error_message': errorMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.LocalAssetUploadEntityCompanion copyWith({
|
||||||
|
i0.Value<String>? assetId,
|
||||||
|
i0.Value<int>? numberOfAttempts,
|
||||||
|
i0.Value<DateTime>? lastAttemptAt,
|
||||||
|
i0.Value<i2.UploadErrorType>? errorType,
|
||||||
|
i0.Value<String?>? errorMessage,
|
||||||
|
}) {
|
||||||
|
return i1.LocalAssetUploadEntityCompanion(
|
||||||
|
assetId: assetId ?? this.assetId,
|
||||||
|
numberOfAttempts: numberOfAttempts ?? this.numberOfAttempts,
|
||||||
|
lastAttemptAt: lastAttemptAt ?? this.lastAttemptAt,
|
||||||
|
errorType: errorType ?? this.errorType,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (assetId.present) {
|
||||||
|
map['asset_id'] = i0.Variable<String>(assetId.value);
|
||||||
|
}
|
||||||
|
if (numberOfAttempts.present) {
|
||||||
|
map['number_of_attempts'] = i0.Variable<int>(numberOfAttempts.value);
|
||||||
|
}
|
||||||
|
if (lastAttemptAt.present) {
|
||||||
|
map['last_attempt_at'] = i0.Variable<DateTime>(lastAttemptAt.value);
|
||||||
|
}
|
||||||
|
if (errorType.present) {
|
||||||
|
map['error_type'] = i0.Variable<int>(
|
||||||
|
i1.$LocalAssetUploadEntityTable.$convertererrorType.toSql(
|
||||||
|
errorType.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (errorMessage.present) {
|
||||||
|
map['error_message'] = i0.Variable<String>(errorMessage.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('LocalAssetUploadEntityCompanion(')
|
||||||
|
..write('assetId: $assetId, ')
|
||||||
|
..write('numberOfAttempts: $numberOfAttempts, ')
|
||||||
|
..write('lastAttemptAt: $lastAttemptAt, ')
|
||||||
|
..write('errorType: $errorType, ')
|
||||||
|
..write('errorMessage: $errorMessage')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
|
enum SortCandidatesBy { createdAt, attemptCount }
|
||||||
|
|
||||||
final backupRepositoryProvider = Provider<DriftBackupRepository>(
|
final backupRepositoryProvider = Provider<DriftBackupRepository>(
|
||||||
(ref) => DriftBackupRepository(ref.watch(driftProvider)),
|
(ref) => DriftBackupRepository(ref.watch(driftProvider)),
|
||||||
);
|
);
|
||||||
@@ -81,37 +83,67 @@ class DriftBackupRepository extends DriftDatabaseRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<LocalAsset>> getCandidates(String userId, {bool onlyHashed = true}) async {
|
Future<List<LocalAsset>> getCandidates(
|
||||||
|
String userId, {
|
||||||
|
bool onlyHashed = true,
|
||||||
|
bool ignoreFailed = false,
|
||||||
|
int? limit,
|
||||||
|
SortCandidatesBy sortBy = SortCandidatesBy.createdAt,
|
||||||
|
}) async {
|
||||||
final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true)
|
final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true)
|
||||||
..addColumns([_db.localAlbumEntity.id])
|
..addColumns([_db.localAlbumEntity.id])
|
||||||
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected));
|
..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected));
|
||||||
|
|
||||||
final query = _db.localAssetEntity.select()
|
final query =
|
||||||
..where(
|
_db.localAssetEntity.select().join([
|
||||||
(lae) =>
|
leftOuterJoin(
|
||||||
existsQuery(
|
_db.localAssetUploadEntity,
|
||||||
_db.localAlbumAssetEntity.selectOnly()
|
_db.localAssetEntity.id.equalsExp(_db.localAssetUploadEntity.assetId),
|
||||||
..addColumns([_db.localAlbumAssetEntity.assetId])
|
useColumns: false,
|
||||||
..where(
|
),
|
||||||
_db.localAlbumAssetEntity.albumId.isInQuery(selectedAlbumIds) &
|
])..where(
|
||||||
_db.localAlbumAssetEntity.assetId.equalsExp(lae.id),
|
existsQuery(
|
||||||
),
|
_db.localAlbumAssetEntity.selectOnly()
|
||||||
) &
|
..addColumns([_db.localAlbumAssetEntity.assetId])
|
||||||
notExistsQuery(
|
..where(
|
||||||
_db.remoteAssetEntity.selectOnly()
|
_db.localAlbumAssetEntity.albumId.isInQuery(selectedAlbumIds) &
|
||||||
..addColumns([_db.remoteAssetEntity.checksum])
|
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
|
||||||
..where(
|
),
|
||||||
_db.remoteAssetEntity.checksum.equalsExp(lae.checksum) & _db.remoteAssetEntity.ownerId.equals(userId),
|
) &
|
||||||
),
|
notExistsQuery(
|
||||||
) &
|
_db.remoteAssetEntity.selectOnly()
|
||||||
lae.id.isNotInQuery(_getExcludedSubquery()),
|
..addColumns([_db.remoteAssetEntity.checksum])
|
||||||
)
|
..where(
|
||||||
..orderBy([(localAsset) => OrderingTerm.desc(localAsset.createdAt)]);
|
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum) &
|
||||||
|
_db.remoteAssetEntity.ownerId.equals(userId),
|
||||||
|
),
|
||||||
|
) &
|
||||||
|
_db.localAssetEntity.id.isNotInQuery(_getExcludedSubquery()),
|
||||||
|
);
|
||||||
|
|
||||||
if (onlyHashed) {
|
switch (sortBy) {
|
||||||
query.where((lae) => lae.checksum.isNotNull());
|
case SortCandidatesBy.createdAt:
|
||||||
|
query.orderBy([OrderingTerm.asc(_db.localAssetEntity.createdAt)]);
|
||||||
|
case SortCandidatesBy.attemptCount:
|
||||||
|
query.orderBy([
|
||||||
|
OrderingTerm.asc(_db.localAssetUploadEntity.numberOfAttempts, nulls: NullsOrder.first),
|
||||||
|
OrderingTerm.asc(_db.localAssetUploadEntity.lastAttemptAt, nulls: NullsOrder.first),
|
||||||
|
OrderingTerm.asc(_db.localAssetEntity.createdAt),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.map((localAsset) => localAsset.toDto()).get();
|
if (onlyHashed) {
|
||||||
|
query.where(_db.localAssetEntity.checksum.isNotNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreFailed) {
|
||||||
|
query.where(_db.localAssetUploadEntity.assetId.isNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit != null) {
|
||||||
|
query.limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset_upload_entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||||
@@ -50,6 +51,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
|||||||
PartnerEntity,
|
PartnerEntity,
|
||||||
LocalAlbumEntity,
|
LocalAlbumEntity,
|
||||||
LocalAssetEntity,
|
LocalAssetEntity,
|
||||||
|
LocalAssetUploadEntity,
|
||||||
LocalAlbumAssetEntity,
|
LocalAlbumAssetEntity,
|
||||||
RemoteAssetEntity,
|
RemoteAssetEntity,
|
||||||
RemoteExifEntity,
|
RemoteExifEntity,
|
||||||
@@ -93,7 +95,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 12;
|
int get schemaVersion => 13;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -178,6 +180,9 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
from12To13: (m, v13) async {
|
||||||
|
await m.createTable(v13.localAssetUploadEntity);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -21,25 +21,27 @@ import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift
|
|||||||
as i9;
|
as i9;
|
||||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'
|
||||||
as i10;
|
as i10;
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/local_asset_upload_entity.drift.dart'
|
||||||
as i11;
|
as i11;
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'
|
||||||
as i12;
|
as i12;
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'
|
||||||
as i13;
|
as i13;
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
||||||
as i14;
|
as i14;
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
||||||
as i15;
|
as i15;
|
||||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
||||||
as i16;
|
as i16;
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
|
||||||
as i17;
|
as i17;
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
||||||
as i18;
|
as i18;
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
||||||
as i19;
|
as i19;
|
||||||
import 'package:drift/internal/modular.dart' as i20;
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
|
as i20;
|
||||||
|
import 'package:drift/internal/modular.dart' as i21;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@@ -64,22 +66,24 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
late final i10.$PartnerEntityTable partnerEntity = i10.$PartnerEntityTable(
|
late final i10.$PartnerEntityTable partnerEntity = i10.$PartnerEntityTable(
|
||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
late final i11.$RemoteExifEntityTable remoteExifEntity = i11
|
late final i11.$LocalAssetUploadEntityTable localAssetUploadEntity = i11
|
||||||
|
.$LocalAssetUploadEntityTable(this);
|
||||||
|
late final i12.$RemoteExifEntityTable remoteExifEntity = i12
|
||||||
.$RemoteExifEntityTable(this);
|
.$RemoteExifEntityTable(this);
|
||||||
late final i12.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity = i12
|
late final i13.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity = i13
|
||||||
.$RemoteAlbumAssetEntityTable(this);
|
.$RemoteAlbumAssetEntityTable(this);
|
||||||
late final i13.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i13
|
late final i14.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i14
|
||||||
.$RemoteAlbumUserEntityTable(this);
|
.$RemoteAlbumUserEntityTable(this);
|
||||||
late final i14.$MemoryEntityTable memoryEntity = i14.$MemoryEntityTable(this);
|
late final i15.$MemoryEntityTable memoryEntity = i15.$MemoryEntityTable(this);
|
||||||
late final i15.$MemoryAssetEntityTable memoryAssetEntity = i15
|
late final i16.$MemoryAssetEntityTable memoryAssetEntity = i16
|
||||||
.$MemoryAssetEntityTable(this);
|
.$MemoryAssetEntityTable(this);
|
||||||
late final i16.$PersonEntityTable personEntity = i16.$PersonEntityTable(this);
|
late final i17.$PersonEntityTable personEntity = i17.$PersonEntityTable(this);
|
||||||
late final i17.$AssetFaceEntityTable assetFaceEntity = i17
|
late final i18.$AssetFaceEntityTable assetFaceEntity = i18
|
||||||
.$AssetFaceEntityTable(this);
|
.$AssetFaceEntityTable(this);
|
||||||
late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this);
|
late final i19.$StoreEntityTable storeEntity = i19.$StoreEntityTable(this);
|
||||||
i19.MergedAssetDrift get mergedAssetDrift => i20.ReadDatabaseContainer(
|
i20.MergedAssetDrift get mergedAssetDrift => i21.ReadDatabaseContainer(
|
||||||
this,
|
this,
|
||||||
).accessor<i19.MergedAssetDrift>(i19.MergedAssetDrift.new);
|
).accessor<i20.MergedAssetDrift>(i20.MergedAssetDrift.new);
|
||||||
@override
|
@override
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||||
@@ -100,6 +104,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
authUserEntity,
|
authUserEntity,
|
||||||
userMetadataEntity,
|
userMetadataEntity,
|
||||||
partnerEntity,
|
partnerEntity,
|
||||||
|
localAssetUploadEntity,
|
||||||
remoteExifEntity,
|
remoteExifEntity,
|
||||||
remoteAlbumAssetEntity,
|
remoteAlbumAssetEntity,
|
||||||
remoteAlbumUserEntity,
|
remoteAlbumUserEntity,
|
||||||
@@ -108,7 +113,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
personEntity,
|
personEntity,
|
||||||
assetFaceEntity,
|
assetFaceEntity,
|
||||||
storeEntity,
|
storeEntity,
|
||||||
i11.idxLatLng,
|
i12.idxLatLng,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
i0.StreamQueryUpdateRules
|
i0.StreamQueryUpdateRules
|
||||||
@@ -197,6 +202,15 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
),
|
),
|
||||||
result: [i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete)],
|
result: [i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete)],
|
||||||
),
|
),
|
||||||
|
i0.WritePropagation(
|
||||||
|
on: i0.TableUpdateQuery.onTableName(
|
||||||
|
'local_asset_entity',
|
||||||
|
limitUpdateKind: i0.UpdateKind.delete,
|
||||||
|
),
|
||||||
|
result: [
|
||||||
|
i0.TableUpdate('local_asset_upload_entity', kind: i0.UpdateKind.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
i0.WritePropagation(
|
i0.WritePropagation(
|
||||||
on: i0.TableUpdateQuery.onTableName(
|
on: i0.TableUpdateQuery.onTableName(
|
||||||
'remote_asset_entity',
|
'remote_asset_entity',
|
||||||
@@ -317,23 +331,28 @@ class $DriftManager {
|
|||||||
i9.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
i9.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity);
|
||||||
i10.$$PartnerEntityTableTableManager get partnerEntity =>
|
i10.$$PartnerEntityTableTableManager get partnerEntity =>
|
||||||
i10.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
i10.$$PartnerEntityTableTableManager(_db, _db.partnerEntity);
|
||||||
i11.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
i11.$$LocalAssetUploadEntityTableTableManager get localAssetUploadEntity =>
|
||||||
i11.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
i11.$$LocalAssetUploadEntityTableTableManager(
|
||||||
i12.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
|
_db,
|
||||||
i12.$$RemoteAlbumAssetEntityTableTableManager(
|
_db.localAssetUploadEntity,
|
||||||
|
);
|
||||||
|
i12.$$RemoteExifEntityTableTableManager get remoteExifEntity =>
|
||||||
|
i12.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity);
|
||||||
|
i13.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity =>
|
||||||
|
i13.$$RemoteAlbumAssetEntityTableTableManager(
|
||||||
_db,
|
_db,
|
||||||
_db.remoteAlbumAssetEntity,
|
_db.remoteAlbumAssetEntity,
|
||||||
);
|
);
|
||||||
i13.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i13
|
i14.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i14
|
||||||
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
|
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
|
||||||
i14.$$MemoryEntityTableTableManager get memoryEntity =>
|
i15.$$MemoryEntityTableTableManager get memoryEntity =>
|
||||||
i14.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
i15.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
||||||
i15.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
i16.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
||||||
i15.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
i16.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
||||||
i16.$$PersonEntityTableTableManager get personEntity =>
|
i17.$$PersonEntityTableTableManager get personEntity =>
|
||||||
i16.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
i17.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
||||||
i17.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
i18.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
||||||
i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
i18.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
||||||
i18.$$StoreEntityTableTableManager get storeEntity =>
|
i19.$$StoreEntityTableTableManager get storeEntity =>
|
||||||
i18.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
i19.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5037,6 +5037,441 @@ final class Schema12 extends i0.VersionedSchema {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Schema13 extends i0.VersionedSchema {
|
||||||
|
Schema13({required super.database}) : super(version: 13);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
userEntity,
|
||||||
|
remoteAssetEntity,
|
||||||
|
stackEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
remoteAlbumEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
idxLocalAssetChecksum,
|
||||||
|
idxRemoteAssetOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerLibraryChecksum,
|
||||||
|
idxRemoteAssetChecksum,
|
||||||
|
authUserEntity,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
localAssetUploadEntity,
|
||||||
|
remoteExifEntity,
|
||||||
|
remoteAlbumAssetEntity,
|
||||||
|
remoteAlbumUserEntity,
|
||||||
|
memoryEntity,
|
||||||
|
memoryAssetEntity,
|
||||||
|
personEntity,
|
||||||
|
assetFaceEntity,
|
||||||
|
storeEntity,
|
||||||
|
idxLatLng,
|
||||||
|
];
|
||||||
|
late final Shape20 userEntity = Shape20(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_3,
|
||||||
|
_column_84,
|
||||||
|
_column_85,
|
||||||
|
_column_91,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape17 remoteAssetEntity = Shape17(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_13,
|
||||||
|
_column_14,
|
||||||
|
_column_15,
|
||||||
|
_column_16,
|
||||||
|
_column_17,
|
||||||
|
_column_18,
|
||||||
|
_column_19,
|
||||||
|
_column_20,
|
||||||
|
_column_21,
|
||||||
|
_column_86,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape3 stackEntity = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'stack_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape2 localAssetEntity = Shape2(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_1,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_0,
|
||||||
|
_column_22,
|
||||||
|
_column_14,
|
||||||
|
_column_23,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape9 remoteAlbumEntity = Shape9(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_56,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_57,
|
||||||
|
_column_58,
|
||||||
|
_column_59,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape19 localAlbumEntity = Shape19(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_5,
|
||||||
|
_column_31,
|
||||||
|
_column_32,
|
||||||
|
_column_90,
|
||||||
|
_column_33,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape22 localAlbumAssetEntity = Shape22(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_34, _column_35, _column_33],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_owner_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_library_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
late final Shape21 authUserEntity = Shape21(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'auth_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_3,
|
||||||
|
_column_2,
|
||||||
|
_column_84,
|
||||||
|
_column_85,
|
||||||
|
_column_92,
|
||||||
|
_column_93,
|
||||||
|
_column_7,
|
||||||
|
_column_94,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape4 userMetadataEntity = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_metadata_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||||
|
columns: [_column_25, _column_26, _column_27],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape5 partnerEntity = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'partner_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||||
|
columns: [_column_28, _column_29, _column_30],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape23 localAssetUploadEntity = Shape23(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_upload_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [_column_34, _column_95, _column_96, _column_97, _column_98],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape8 remoteExifEntity = Shape8(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_exif_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_36,
|
||||||
|
_column_37,
|
||||||
|
_column_38,
|
||||||
|
_column_39,
|
||||||
|
_column_40,
|
||||||
|
_column_41,
|
||||||
|
_column_11,
|
||||||
|
_column_10,
|
||||||
|
_column_42,
|
||||||
|
_column_43,
|
||||||
|
_column_44,
|
||||||
|
_column_45,
|
||||||
|
_column_46,
|
||||||
|
_column_47,
|
||||||
|
_column_48,
|
||||||
|
_column_49,
|
||||||
|
_column_50,
|
||||||
|
_column_51,
|
||||||
|
_column_52,
|
||||||
|
_column_53,
|
||||||
|
_column_54,
|
||||||
|
_column_55,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_36, _column_60],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||||
|
columns: [_column_60, _column_25, _column_61],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape11 memoryEntity = Shape11(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_18,
|
||||||
|
_column_15,
|
||||||
|
_column_8,
|
||||||
|
_column_62,
|
||||||
|
_column_63,
|
||||||
|
_column_64,
|
||||||
|
_column_65,
|
||||||
|
_column_66,
|
||||||
|
_column_67,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape12 memoryAssetEntity = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||||
|
columns: [_column_36, _column_68],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape14 personEntity = Shape14(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'person_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_9,
|
||||||
|
_column_5,
|
||||||
|
_column_15,
|
||||||
|
_column_1,
|
||||||
|
_column_69,
|
||||||
|
_column_71,
|
||||||
|
_column_72,
|
||||||
|
_column_73,
|
||||||
|
_column_74,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape15 assetFaceEntity = Shape15(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_face_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_36,
|
||||||
|
_column_76,
|
||||||
|
_column_77,
|
||||||
|
_column_78,
|
||||||
|
_column_79,
|
||||||
|
_column_80,
|
||||||
|
_column_81,
|
||||||
|
_column_82,
|
||||||
|
_column_83,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape18 storeEntity = Shape18(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'store_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_87, _column_88, _column_89],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLatLng = i1.Index(
|
||||||
|
'idx_lat_lng',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape23 extends i0.VersionedTable {
|
||||||
|
Shape23({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get assetId =>
|
||||||
|
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get numberOfAttempts =>
|
||||||
|
columnsByName['number_of_attempts']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<DateTime> get lastAttemptAt =>
|
||||||
|
columnsByName['last_attempt_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<int> get errorType =>
|
||||||
|
columnsByName['error_type']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get errorMessage =>
|
||||||
|
columnsByName['error_message']! as i1.GeneratedColumn<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_95(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'number_of_attempts',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
defaultValue: const CustomExpression('0'),
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<DateTime> _column_96(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>(
|
||||||
|
'last_attempt_at',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.dateTime,
|
||||||
|
defaultValue: const CustomExpression('CURRENT_TIMESTAMP'),
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_97(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'error_type',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
defaultValue: const CustomExpression('0'),
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<String> _column_98(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>(
|
||||||
|
'error_message',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@@ -5049,6 +5484,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -5107,6 +5543,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from11To12(migrator, schema);
|
await from11To12(migrator, schema);
|
||||||
return 12;
|
return 12;
|
||||||
|
case 12:
|
||||||
|
final schema = Schema13(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from12To13(migrator, schema);
|
||||||
|
return 13;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -5125,6 +5566,7 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -5138,5 +5580,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from9To10: from9To10,
|
from9To10: from9To10,
|
||||||
from10To11: from10To11,
|
from10To11: from10To11,
|
||||||
from11To12: from11To12,
|
from11To12: from11To12,
|
||||||
|
from12To13: from12To13,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset_upload_entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
|
|
||||||
|
class DriftLocalAssetUploadRepository extends DriftDatabaseRepository {
|
||||||
|
final Drift _db;
|
||||||
|
const DriftLocalAssetUploadRepository(this._db) : super(_db);
|
||||||
|
|
||||||
|
Stream<List<DriftUploadStatus>> watchAll() {
|
||||||
|
final query = _db.localAssetUploadEntity.select().addColumns([_db.localAssetEntity.name]).join([
|
||||||
|
leftOuterJoin(
|
||||||
|
_db.localAssetEntity,
|
||||||
|
_db.localAssetEntity.id.equalsExp(_db.localAssetUploadEntity.assetId),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
return query.map((row) {
|
||||||
|
final upload = row.readTable(_db.localAssetUploadEntity);
|
||||||
|
final assetName = row.read(_db.localAssetEntity.name)!;
|
||||||
|
return DriftUploadStatus(taskId: upload.assetId, filename: assetName, error: upload.errorMessage, isFailed: true);
|
||||||
|
}).watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> upsert(String assetId, UploadErrorType errorType, String error) {
|
||||||
|
return _db
|
||||||
|
.into(_db.localAssetUploadEntity)
|
||||||
|
.insert(
|
||||||
|
LocalAssetUploadEntityCompanion(
|
||||||
|
assetId: Value(assetId),
|
||||||
|
numberOfAttempts: const Value(1),
|
||||||
|
lastAttemptAt: Value(DateTime.now()),
|
||||||
|
errorType: Value(errorType),
|
||||||
|
errorMessage: Value(error),
|
||||||
|
),
|
||||||
|
onConflict: DoUpdate(
|
||||||
|
(old) => LocalAssetUploadEntityCompanion.custom(
|
||||||
|
numberOfAttempts: (old.numberOfAttempts + const Constant(1)),
|
||||||
|
lastAttemptAt: currentDateAndTime,
|
||||||
|
errorType: Variable.withInt(errorType.index),
|
||||||
|
errorMessage: Variable.withString(error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> delete(String assetId) async {
|
||||||
|
await _db.managers.localAssetUploadEntity.filter((row) => row.assetId.id.equals(assetId)).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> prune() async {
|
||||||
|
final query =
|
||||||
|
_db.localAssetUploadEntity.selectOnly().join([
|
||||||
|
leftOuterJoin(
|
||||||
|
_db.localAssetEntity,
|
||||||
|
_db.localAssetUploadEntity.assetId.equalsExp(_db.localAssetEntity.id),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
leftOuterJoin(
|
||||||
|
_db.remoteAssetEntity,
|
||||||
|
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
..where(_db.remoteAssetEntity.checksum.isNotNull())
|
||||||
|
..addColumns([_db.localAssetUploadEntity.assetId]);
|
||||||
|
await _db.localAssetUploadEntity.deleteWhere((row) => row.assetId.isInQuery(query));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,60 +82,6 @@ class StorageRepository {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if an asset is available locally or needs to be downloaded from iCloud
|
|
||||||
Future<bool> isAssetAvailableLocally(String assetId) async {
|
|
||||||
final log = Logger('StorageRepository');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final entity = await AssetEntity.fromId(assetId);
|
|
||||||
if (entity == null) {
|
|
||||||
log.warning("Cannot get AssetEntity for asset $assetId");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await entity.isLocallyAvailable(isOrigin: true);
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
log.warning("Error checking if asset is locally available $assetId", error, stackTrace);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load file from iCloud with progress handler (for iOS)
|
|
||||||
Future<File?> loadFileFromCloud(String assetId, {PMProgressHandler? progressHandler}) async {
|
|
||||||
final log = Logger('StorageRepository');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final entity = await AssetEntity.fromId(assetId);
|
|
||||||
if (entity == null) {
|
|
||||||
log.warning("Cannot get AssetEntity for asset $assetId");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await entity.loadFile(progressHandler: progressHandler);
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
log.warning("Error loading file from cloud for asset $assetId", error, stackTrace);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load live photo motion file from iCloud with progress handler (for iOS)
|
|
||||||
Future<File?> loadMotionFileFromCloud(String assetId, {PMProgressHandler? progressHandler}) async {
|
|
||||||
final log = Logger('StorageRepository');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final entity = await AssetEntity.fromId(assetId);
|
|
||||||
if (entity == null) {
|
|
||||||
log.warning("Cannot get AssetEntity for asset $assetId");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await entity.loadFile(withSubtype: true, progressHandler: progressHandler);
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
log.warning("Error loading motion file from cloud for asset $assetId", error, stackTrace);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearCache() async {
|
Future<void> clearCache() async {
|
||||||
final log = Logger('StorageRepository');
|
final log = Logger('StorageRepository');
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provide
|
|||||||
import 'package:immich_mobile/providers/db.provider.dart';
|
import 'package:immich_mobile/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/locale_provider.dart';
|
import 'package:immich_mobile/providers/locale_provider.dart';
|
||||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
import 'package:immich_mobile/providers/theme.provider.dart';
|
import 'package:immich_mobile/providers/theme.provider.dart';
|
||||||
@@ -211,6 +212,8 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
|||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// needs to be delayed so that EasyLocalization is working
|
// needs to be delayed so that EasyLocalization is working
|
||||||
if (Store.isBetaTimelineEnabled) {
|
if (Store.isBetaTimelineEnabled) {
|
||||||
|
// Start upload timer
|
||||||
|
ref.read(uploadTimerProvider.notifier).start();
|
||||||
ref.read(backgroundServiceProvider).disableService();
|
ref.read(backgroundServiceProvider).disableService();
|
||||||
ref.read(backgroundWorkerFgServiceProvider).enable();
|
ref.read(backgroundWorkerFgServiceProvider).enable();
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
|
|||||||
@@ -97,8 +97,7 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stopBackup() async {
|
Future<void> stopBackup() async {
|
||||||
// await backupNotifier.cancel();
|
await backupNotifier.cancel();
|
||||||
await backupNotifier.stopBackup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
@@ -7,6 +8,8 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
|
|||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/upload.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/upload.service.dart';
|
||||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
@@ -16,8 +19,10 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final uploadItems = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
final inProgressUploads = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
||||||
final iCloudProgress = ref.watch(driftBackupProvider.select((state) => state.iCloudDownloadProgress));
|
final failedUploads = ref.watch(failedUploadStatusProvider).valueOrNull ?? {};
|
||||||
|
// In-progress uploads take precedence over failed uploads with the same key
|
||||||
|
final uploadItems = {...failedUploads, ...inProgressUploads};
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -26,9 +31,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 1,
|
scrolledUnderElevation: 1,
|
||||||
),
|
),
|
||||||
body: uploadItems.isEmpty && iCloudProgress.isEmpty
|
body: uploadItems.isEmpty ? _buildEmptyState(context) : _buildUploadList(uploadItems),
|
||||||
? _buildEmptyState(context)
|
|
||||||
: _buildUploadList(uploadItems, iCloudProgress),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,113 +51,22 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUploadList(Map<String, DriftUploadStatus> uploadItems, Map<String, double> iCloudProgress) {
|
Widget _buildUploadList(Map<String, DriftUploadStatus> uploadItems) {
|
||||||
final totalItems = uploadItems.length + iCloudProgress.length;
|
final sortedKeys = uploadItems.keys.sorted();
|
||||||
|
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
addAutomaticKeepAlives: true,
|
addAutomaticKeepAlives: true,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
itemCount: totalItems,
|
itemCount: sortedKeys.length,
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 4),
|
separatorBuilder: (context, index) => const SizedBox(height: 4),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
// Show iCloud downloads first
|
final item = uploadItems[sortedKeys[index]]!;
|
||||||
if (index < iCloudProgress.length) {
|
|
||||||
final entry = iCloudProgress.entries.elementAt(index);
|
|
||||||
return _buildICloudDownloadCard(context, entry.key, entry.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then show upload items
|
|
||||||
final uploadIndex = index - iCloudProgress.length;
|
|
||||||
final item = uploadItems.values.elementAt(uploadIndex);
|
|
||||||
return _buildUploadCard(context, item);
|
return _buildUploadCard(context, item);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildICloudDownloadCard(BuildContext context, String assetId, double progress) {
|
|
||||||
final double progressPercentage = (progress * 100).clamp(0, 100);
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
elevation: 0,
|
|
||||||
color: context.colorScheme.surfaceContainerHighest,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
|
||||||
side: BorderSide(color: context.colorScheme.outline.withValues(alpha: 0.1), width: 1),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.cloud_download_rounded, size: 20, color: context.colorScheme.primary),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Downloading from iCloud",
|
|
||||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
assetId,
|
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
|
||||||
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildICloudProgressIndicator(context, progress, progressPercentage),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildICloudProgressIndicator(BuildContext context, double progress, double percentage) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Stack(
|
|
||||||
alignment: AlignmentDirectional.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
child: TweenAnimationBuilder(
|
|
||||||
tween: Tween<double>(begin: 0.0, end: progress),
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
builder: (context, value, _) => CircularProgressIndicator(
|
|
||||||
backgroundColor: context.colorScheme.outline.withValues(alpha: 0.2),
|
|
||||||
strokeWidth: 3,
|
|
||||||
value: value,
|
|
||||||
color: context.colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
percentage.toStringAsFixed(0),
|
|
||||||
style: context.textTheme.labelSmall?.copyWith(fontWeight: FontWeight.bold, fontSize: 10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text("iCloud", style: context.textTheme.labelSmall?.copyWith(color: context.colorScheme.primary, fontSize: 10)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildUploadCard(BuildContext context, DriftUploadStatus item) {
|
Widget _buildUploadCard(BuildContext context, DriftUploadStatus item) {
|
||||||
final isCompleted = item.progress >= 1.0;
|
final progress = item.progress;
|
||||||
final double progressPercentage = (item.progress * 100).clamp(0, 100);
|
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
@@ -202,13 +114,16 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildProgressIndicator(
|
if (item.isFailed == true)
|
||||||
context,
|
_buildRetryButton(item)
|
||||||
item.progress,
|
else if (progress != null)
|
||||||
progressPercentage,
|
_buildProgressIndicator(
|
||||||
isCompleted,
|
context,
|
||||||
item.networkSpeedAsString,
|
progress,
|
||||||
),
|
(progress * 100).clamp(0, 100),
|
||||||
|
progress >= 1.0,
|
||||||
|
item.networkSpeedAsString,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -223,7 +138,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
double progress,
|
double progress,
|
||||||
double percentage,
|
double percentage,
|
||||||
bool isCompleted,
|
bool isCompleted,
|
||||||
String networkSpeedAsString,
|
String? networkSpeedAsString,
|
||||||
) {
|
) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -253,17 +168,38 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
if (networkSpeedAsString != null)
|
||||||
networkSpeedAsString,
|
Text(
|
||||||
style: context.textTheme.labelSmall?.copyWith(
|
networkSpeedAsString,
|
||||||
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
style: context.textTheme.labelSmall?.copyWith(
|
||||||
fontSize: 10,
|
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildRetryButton(DriftUploadStatus item) {
|
||||||
|
return Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () => _retryFailedUpload(ref, item),
|
||||||
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
|
iconSize: 24,
|
||||||
|
color: context.colorScheme.onErrorContainer,
|
||||||
|
tooltip: "retry_upload".t(context: context),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _retryFailedUpload(WidgetRef ref, DriftUploadStatus item) async {
|
||||||
|
await ref.read(uploadServiceProvider).clearError(item.taskId);
|
||||||
|
ref.invalidate(failedUploadStatusProvider);
|
||||||
|
await ref.read(uploadServiceProvider).manualBackupId(item.taskId);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) {
|
Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -335,7 +271,8 @@ class FileDetailDialog extends ConsumerWidget {
|
|||||||
_buildInfoSection(context, [
|
_buildInfoSection(context, [
|
||||||
_buildInfoRow(context, "Filename", path.basename(uploadStatus.filename)),
|
_buildInfoRow(context, "Filename", path.basename(uploadStatus.filename)),
|
||||||
_buildInfoRow(context, "Local ID", asset.id),
|
_buildInfoRow(context, "Local ID", asset.id),
|
||||||
_buildInfoRow(context, "File Size", formatHumanReadableBytes(uploadStatus.fileSize, 2)),
|
if (uploadStatus.fileSize != null)
|
||||||
|
_buildInfoRow(context, "File Size", formatHumanReadableBytes(uploadStatus.fileSize!, 2)),
|
||||||
if (asset.width != null) _buildInfoRow(context, "Width", "${asset.width}px"),
|
if (asset.width != null) _buildInfoRow(context, "Width", "${asset.width}px"),
|
||||||
if (asset.height != null) _buildInfoRow(context, "Height", "${asset.height}px"),
|
if (asset.height != null) _buildInfoRow(context, "Height", "${asset.height}px"),
|
||||||
_buildInfoRow(context, "Created At", asset.createdAt.toString()),
|
_buildInfoRow(context, "Created At", asset.createdAt.toString()),
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
|
|
||||||
final isSyncing = ref.watch(driftBackupProvider.select((state) => state.isSyncing));
|
final isSyncing = ref.watch(driftBackupProvider.select((state) => state.isSyncing));
|
||||||
|
|
||||||
final iCloudProgress = ref.watch(driftBackupProvider.select((state) => state.iCloudDownloadProgress));
|
final isProcessing = uploadTasks.isNotEmpty || isSyncing;
|
||||||
|
|
||||||
final isProcessing = uploadTasks.isNotEmpty || isSyncing || iCloudProgress.isNotEmpty;
|
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: _animationController,
|
animation: _animationController,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:immich_mobile/providers/backup/ios_background_settings.provider.
|
|||||||
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/memory.provider.dart';
|
import 'package:immich_mobile/providers/memory.provider.dart';
|
||||||
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
@@ -139,6 +140,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
|
|
||||||
Future<void> _handleBetaTimelineResume() async {
|
Future<void> _handleBetaTimelineResume() async {
|
||||||
_ref.read(backupProvider.notifier).cancelBackup();
|
_ref.read(backupProvider.notifier).cancelBackup();
|
||||||
|
_ref.read(uploadTimerProvider.notifier).start();
|
||||||
unawaited(_ref.read(backgroundWorkerLockServiceProvider).lock());
|
unawaited(_ref.read(backgroundWorkerLockServiceProvider).lock());
|
||||||
|
|
||||||
// Give isolates time to complete any ongoing database transactions
|
// Give isolates time to complete any ongoing database transactions
|
||||||
@@ -216,6 +218,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (Store.isBetaTimelineEnabled) {
|
if (Store.isBetaTimelineEnabled) {
|
||||||
|
_ref.read(uploadTimerProvider.notifier).stop();
|
||||||
unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock());
|
unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock());
|
||||||
}
|
}
|
||||||
await _performPause();
|
await _performPause();
|
||||||
@@ -250,6 +253,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
state = AppLifeCycleEnum.detached;
|
state = AppLifeCycleEnum.detached;
|
||||||
|
|
||||||
if (Store.isBetaTimelineEnabled) {
|
if (Store.isBetaTimelineEnabled) {
|
||||||
|
_ref.read(uploadTimerProvider.notifier).stop();
|
||||||
unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock());
|
unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:cancellation_token_http/http.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
import 'package:immich_mobile/extensions/string_extensions.dart';
|
||||||
@@ -15,6 +14,7 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/upload.service.dart';
|
import 'package:immich_mobile/services/upload.service.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class EnqueueStatus {
|
class EnqueueStatus {
|
||||||
final int enqueueCount;
|
final int enqueueCount;
|
||||||
@@ -33,18 +33,18 @@ class EnqueueStatus {
|
|||||||
class DriftUploadStatus {
|
class DriftUploadStatus {
|
||||||
final String taskId;
|
final String taskId;
|
||||||
final String filename;
|
final String filename;
|
||||||
final double progress;
|
final double? progress;
|
||||||
final int fileSize;
|
final int? fileSize;
|
||||||
final String networkSpeedAsString;
|
final String? networkSpeedAsString;
|
||||||
final bool? isFailed;
|
final bool? isFailed;
|
||||||
final String? error;
|
final String? error;
|
||||||
|
|
||||||
const DriftUploadStatus({
|
const DriftUploadStatus({
|
||||||
required this.taskId,
|
required this.taskId,
|
||||||
required this.filename,
|
required this.filename,
|
||||||
required this.progress,
|
this.progress,
|
||||||
required this.fileSize,
|
this.fileSize,
|
||||||
required this.networkSpeedAsString,
|
this.networkSpeedAsString,
|
||||||
this.isFailed,
|
this.isFailed,
|
||||||
this.error,
|
this.error,
|
||||||
});
|
});
|
||||||
@@ -115,10 +115,6 @@ class DriftBackupState {
|
|||||||
final BackupError error;
|
final BackupError error;
|
||||||
|
|
||||||
final Map<String, DriftUploadStatus> uploadItems;
|
final Map<String, DriftUploadStatus> uploadItems;
|
||||||
final CancellationToken? cancelToken;
|
|
||||||
|
|
||||||
/// iCloud download progress for assets (assetId -> progress 0.0-1.0)
|
|
||||||
final Map<String, double> iCloudDownloadProgress;
|
|
||||||
|
|
||||||
const DriftBackupState({
|
const DriftBackupState({
|
||||||
required this.totalCount,
|
required this.totalCount,
|
||||||
@@ -127,12 +123,10 @@ class DriftBackupState {
|
|||||||
required this.processingCount,
|
required this.processingCount,
|
||||||
required this.enqueueCount,
|
required this.enqueueCount,
|
||||||
required this.enqueueTotalCount,
|
required this.enqueueTotalCount,
|
||||||
required this.isSyncing,
|
|
||||||
required this.isCanceling,
|
required this.isCanceling,
|
||||||
this.error = BackupError.none,
|
required this.isSyncing,
|
||||||
required this.uploadItems,
|
required this.uploadItems,
|
||||||
this.cancelToken,
|
this.error = BackupError.none,
|
||||||
this.iCloudDownloadProgress = const {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
DriftBackupState copyWith({
|
DriftBackupState copyWith({
|
||||||
@@ -142,12 +136,10 @@ class DriftBackupState {
|
|||||||
int? processingCount,
|
int? processingCount,
|
||||||
int? enqueueCount,
|
int? enqueueCount,
|
||||||
int? enqueueTotalCount,
|
int? enqueueTotalCount,
|
||||||
bool? isSyncing,
|
|
||||||
bool? isCanceling,
|
bool? isCanceling,
|
||||||
BackupError? error,
|
bool? isSyncing,
|
||||||
Map<String, DriftUploadStatus>? uploadItems,
|
Map<String, DriftUploadStatus>? uploadItems,
|
||||||
CancellationToken? cancelToken,
|
BackupError? error,
|
||||||
Map<String, double>? iCloudDownloadProgress,
|
|
||||||
}) {
|
}) {
|
||||||
return DriftBackupState(
|
return DriftBackupState(
|
||||||
totalCount: totalCount ?? this.totalCount,
|
totalCount: totalCount ?? this.totalCount,
|
||||||
@@ -156,18 +148,16 @@ class DriftBackupState {
|
|||||||
processingCount: processingCount ?? this.processingCount,
|
processingCount: processingCount ?? this.processingCount,
|
||||||
enqueueCount: enqueueCount ?? this.enqueueCount,
|
enqueueCount: enqueueCount ?? this.enqueueCount,
|
||||||
enqueueTotalCount: enqueueTotalCount ?? this.enqueueTotalCount,
|
enqueueTotalCount: enqueueTotalCount ?? this.enqueueTotalCount,
|
||||||
isSyncing: isSyncing ?? this.isSyncing,
|
|
||||||
isCanceling: isCanceling ?? this.isCanceling,
|
isCanceling: isCanceling ?? this.isCanceling,
|
||||||
error: error ?? this.error,
|
isSyncing: isSyncing ?? this.isSyncing,
|
||||||
uploadItems: uploadItems ?? this.uploadItems,
|
uploadItems: uploadItems ?? this.uploadItems,
|
||||||
cancelToken: cancelToken ?? this.cancelToken,
|
error: error ?? this.error,
|
||||||
iCloudDownloadProgress: iCloudDownloadProgress ?? this.iCloudDownloadProgress,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, processingCount: $processingCount, enqueueCount: $enqueueCount, enqueueTotalCount: $enqueueTotalCount, isSyncing: $isSyncing, isCanceling: $isCanceling, error: $error, uploadItems: $uploadItems, cancelToken: $cancelToken, iCloudDownloadProgress: $iCloudDownloadProgress)';
|
return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, processingCount: $processingCount, enqueueCount: $enqueueCount, enqueueTotalCount: $enqueueTotalCount, isCanceling: $isCanceling, isSyncing: $isSyncing, uploadItems: $uploadItems, error: $error)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -181,12 +171,10 @@ class DriftBackupState {
|
|||||||
other.processingCount == processingCount &&
|
other.processingCount == processingCount &&
|
||||||
other.enqueueCount == enqueueCount &&
|
other.enqueueCount == enqueueCount &&
|
||||||
other.enqueueTotalCount == enqueueTotalCount &&
|
other.enqueueTotalCount == enqueueTotalCount &&
|
||||||
other.isSyncing == isSyncing &&
|
|
||||||
other.isCanceling == isCanceling &&
|
other.isCanceling == isCanceling &&
|
||||||
other.error == error &&
|
other.isSyncing == isSyncing &&
|
||||||
mapEquals(other.iCloudDownloadProgress, iCloudDownloadProgress) &&
|
|
||||||
mapEquals(other.uploadItems, uploadItems) &&
|
mapEquals(other.uploadItems, uploadItems) &&
|
||||||
other.cancelToken == cancelToken;
|
other.error == error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -197,12 +185,10 @@ class DriftBackupState {
|
|||||||
processingCount.hashCode ^
|
processingCount.hashCode ^
|
||||||
enqueueCount.hashCode ^
|
enqueueCount.hashCode ^
|
||||||
enqueueTotalCount.hashCode ^
|
enqueueTotalCount.hashCode ^
|
||||||
isSyncing.hashCode ^
|
|
||||||
isCanceling.hashCode ^
|
isCanceling.hashCode ^
|
||||||
error.hashCode ^
|
isSyncing.hashCode ^
|
||||||
uploadItems.hashCode ^
|
uploadItems.hashCode ^
|
||||||
cancelToken.hashCode ^
|
error.hashCode;
|
||||||
iCloudDownloadProgress.hashCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +232,7 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTaskStatusUpdate(TaskStatusUpdate update) {
|
void _handleTaskStatusUpdate(TaskStatusUpdate update) async {
|
||||||
final taskId = update.task.taskId;
|
final taskId = update.task.taskId;
|
||||||
|
|
||||||
switch (update.status) {
|
switch (update.status) {
|
||||||
@@ -264,35 +250,36 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _uploadService.clearError(taskId);
|
||||||
|
|
||||||
case TaskStatus.failed:
|
case TaskStatus.failed:
|
||||||
|
_removeUploadItem(taskId);
|
||||||
// Ignore retry errors to avoid confusing users
|
// Ignore retry errors to avoid confusing users
|
||||||
if (update.exception?.description == 'Delayed or retried enqueue failed') {
|
if (update.exception?.description == 'Delayed or retried enqueue failed') {
|
||||||
_removeUploadItem(taskId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentItem = state.uploadItems[taskId];
|
|
||||||
if (currentItem == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? error;
|
String? error;
|
||||||
final exception = update.exception;
|
final exception = update.exception;
|
||||||
if (exception != null && exception is TaskHttpException) {
|
if (exception != null) {
|
||||||
final message = tryJsonDecode(exception.description)?['message'] as String?;
|
final errorType = switch (exception) {
|
||||||
if (message != null) {
|
TaskConnectionException() => UploadErrorType.network,
|
||||||
final responseCode = exception.httpResponseCode;
|
TaskFileSystemException() || TaskUrlException() => UploadErrorType.client,
|
||||||
error = "${exception.exceptionType}, response code $responseCode: $message";
|
TaskHttpException() => UploadErrorType.server,
|
||||||
}
|
_ => UploadErrorType.unknown,
|
||||||
}
|
};
|
||||||
error ??= update.exception?.toString();
|
|
||||||
|
|
||||||
state = state.copyWith(
|
if (exception is TaskHttpException) {
|
||||||
uploadItems: {
|
final message = tryJsonDecode(exception.description)?['message'] as String?;
|
||||||
...state.uploadItems,
|
if (message != null) {
|
||||||
taskId: currentItem.copyWith(isFailed: true, error: error),
|
final responseCode = exception.httpResponseCode;
|
||||||
},
|
error = "${exception.exceptionType}, response code $responseCode: $message";
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error ??= exception.toString();
|
||||||
|
await _uploadService.updateError(taskId, errorType, error);
|
||||||
|
}
|
||||||
_logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}");
|
_logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -365,84 +352,15 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
|||||||
state = state.copyWith(isSyncing: isSyncing);
|
state = state.copyWith(isSyncing: isSyncing);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startBackup(String userId) {
|
Future<void> startBackup(String userId, {bool ignoreFailed = false}) {
|
||||||
state = state.copyWith(error: BackupError.none);
|
state = state.copyWith(error: BackupError.none);
|
||||||
// return _uploadService.startBackup(userId, _updateEnqueueCount);
|
return _uploadService.startBackup(userId, _updateEnqueueCount, ignoreFailed: ignoreFailed);
|
||||||
|
|
||||||
final cancelToken = CancellationToken();
|
|
||||||
state = state.copyWith(cancelToken: cancelToken);
|
|
||||||
|
|
||||||
return _uploadService.startForegroundUpload(
|
|
||||||
userId,
|
|
||||||
cancelToken,
|
|
||||||
_handleForegroundBackupProgress,
|
|
||||||
_handleForegroundBackupSuccess,
|
|
||||||
_handleForegroundBackupError,
|
|
||||||
onICloudProgress: _handleICloudProgress,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stopBackup() async {
|
void _updateEnqueueCount(EnqueueStatus status) {
|
||||||
state.cancelToken?.cancel();
|
state = state.copyWith(enqueueCount: status.enqueueCount, enqueueTotalCount: status.totalCount);
|
||||||
state = state.copyWith(cancelToken: null, uploadItems: {}, iCloudDownloadProgress: {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleICloudProgress(String localAssetId, double progress) {
|
|
||||||
state = state.copyWith(iCloudDownloadProgress: {...state.iCloudDownloadProgress, localAssetId: progress});
|
|
||||||
|
|
||||||
// Remove from progress map when download completes
|
|
||||||
if (progress >= 1.0) {
|
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
|
||||||
final updatedProgress = Map<String, double>.from(state.iCloudDownloadProgress);
|
|
||||||
updatedProgress.remove(localAssetId);
|
|
||||||
state = state.copyWith(iCloudDownloadProgress: updatedProgress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleForegroundBackupProgress(String localAssetId, int bytes, int totalBytes) {
|
|
||||||
final progress = totalBytes > 0 ? bytes / totalBytes : 0.0;
|
|
||||||
final currentItem = state.uploadItems[localAssetId];
|
|
||||||
if (currentItem != null) {
|
|
||||||
state = state.copyWith(
|
|
||||||
uploadItems: {
|
|
||||||
...state.uploadItems,
|
|
||||||
localAssetId: currentItem.copyWith(progress: progress, fileSize: totalBytes),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
state = state.copyWith(
|
|
||||||
uploadItems: {
|
|
||||||
...state.uploadItems,
|
|
||||||
localAssetId: DriftUploadStatus(
|
|
||||||
taskId: localAssetId,
|
|
||||||
filename: localAssetId,
|
|
||||||
progress: progress,
|
|
||||||
fileSize: totalBytes,
|
|
||||||
networkSpeedAsString: '',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleForegroundBackupSuccess(String localAssetId, String remoteAssetId) {
|
|
||||||
state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1);
|
|
||||||
|
|
||||||
Future.delayed(const Duration(milliseconds: 1000), () {
|
|
||||||
_removeUploadItem(localAssetId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleForegroundBackupError(String errorMessage) {
|
|
||||||
_logger.severe("Upload failed: $errorMessage");
|
|
||||||
// Here you can update the state to reflect the error if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
// void _updateEnqueueCount(EnqueueStatus status) {
|
|
||||||
// state = state.copyWith(enqueueCount: status.enqueueCount, enqueueTotalCount: status.totalCount);
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> cancel() async {
|
Future<void> cancel() async {
|
||||||
dPrint(() => "Canceling backup tasks...");
|
dPrint(() => "Canceling backup tasks...");
|
||||||
state = state.copyWith(enqueueCount: 0, enqueueTotalCount: 0, isCanceling: true, error: BackupError.none);
|
state = state.copyWith(enqueueCount: 0, enqueueTotalCount: 0, isCanceling: true, error: BackupError.none);
|
||||||
|
|||||||
91
mobile/lib/providers/infrastructure/upload.provider.dart
Normal file
91
mobile/lib/providers/infrastructure/upload.provider.dart
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/local_asset_upload.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
class UploadTimerNotifier extends Notifier<bool> {
|
||||||
|
Timer? _timer;
|
||||||
|
final _timerLogger = Logger('UploadTimer');
|
||||||
|
static const _refreshDuration = Duration(seconds: 45);
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
if (state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = true;
|
||||||
|
_schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
state = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _schedule() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = Timer(_refreshDuration, () async {
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _backup();
|
||||||
|
if (state) {
|
||||||
|
_schedule();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _backup() async {
|
||||||
|
final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||||
|
if (!isBackupEnabled) {
|
||||||
|
_timerLogger.fine("UploadTimer: Backup is disabled, skipping backup start.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tasks = await FileDownloader().allTasks(group: kBackupGroup);
|
||||||
|
final currentUserId = ref.read(currentUserProvider)?.id;
|
||||||
|
if (tasks.isEmpty && currentUserId != null) {
|
||||||
|
await ref.read(driftBackupProvider.notifier).startBackup(currentUserId, ignoreFailed: true);
|
||||||
|
} else {
|
||||||
|
_timerLogger.fine("UploadTimer: There are still active upload tasks - ${tasks.length}, skipping backup start.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
Future.microtask(start);
|
||||||
|
ref.onDispose(() {
|
||||||
|
_timer?.cancel();
|
||||||
|
});
|
||||||
|
// Timer is not running yet
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final uploadTimerProvider = NotifierProvider<UploadTimerNotifier, bool>(UploadTimerNotifier.new);
|
||||||
|
|
||||||
|
final assetUploadRepositoryProvider = Provider<DriftLocalAssetUploadRepository>(
|
||||||
|
(ref) => DriftLocalAssetUploadRepository(ref.watch(driftProvider)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final failedUploadStatusProvider = StreamProvider.autoDispose<Map<String, DriftUploadStatus>>((ref) {
|
||||||
|
return ref.watch(assetUploadRepositoryProvider).watchAll().map((uploads) {
|
||||||
|
return uploads.fold<Map<String, DriftUploadStatus>>({}, (acc, upload) {
|
||||||
|
acc[upload.taskId] = upload;
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
@@ -141,153 +140,4 @@ class UploadRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a single asset with progress tracking
|
|
||||||
Future<UploadResult> uploadSingleAsset({
|
|
||||||
required File file,
|
|
||||||
required String originalFileName,
|
|
||||||
required Map<String, String> headers,
|
|
||||||
required Map<String, String> fields,
|
|
||||||
required Client httpClient,
|
|
||||||
required CancellationToken cancelToken,
|
|
||||||
required void Function(int bytes, int totalBytes) onProgress,
|
|
||||||
}) async {
|
|
||||||
return _uploadFile(
|
|
||||||
file: file,
|
|
||||||
originalFileName: originalFileName,
|
|
||||||
headers: headers,
|
|
||||||
fields: fields,
|
|
||||||
httpClient: httpClient,
|
|
||||||
cancelToken: cancelToken,
|
|
||||||
onProgress: onProgress,
|
|
||||||
logContext: 'asset',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Upload live photo video part and return the video asset ID
|
|
||||||
Future<String?> uploadLivePhotoVideo({
|
|
||||||
required File livePhotoFile,
|
|
||||||
required String originalFileName,
|
|
||||||
required Map<String, String> headers,
|
|
||||||
required Map<String, String> fields,
|
|
||||||
required Client httpClient,
|
|
||||||
required CancellationToken cancelToken,
|
|
||||||
required void Function(int bytes, int totalBytes) onProgress,
|
|
||||||
}) async {
|
|
||||||
final result = await _uploadFile(
|
|
||||||
file: livePhotoFile,
|
|
||||||
originalFileName: originalFileName,
|
|
||||||
headers: headers,
|
|
||||||
fields: fields,
|
|
||||||
httpClient: httpClient,
|
|
||||||
cancelToken: cancelToken,
|
|
||||||
onProgress: onProgress,
|
|
||||||
logContext: 'livePhoto video',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.isSuccess && result.remoteAssetId != null) {
|
|
||||||
return result.remoteAssetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal method to upload a file to the server
|
|
||||||
Future<UploadResult> _uploadFile({
|
|
||||||
required File file,
|
|
||||||
required String originalFileName,
|
|
||||||
required Map<String, String> headers,
|
|
||||||
required Map<String, String> fields,
|
|
||||||
required Client httpClient,
|
|
||||||
required CancellationToken cancelToken,
|
|
||||||
required void Function(int bytes, int totalBytes) onProgress,
|
|
||||||
required String logContext,
|
|
||||||
}) async {
|
|
||||||
final String savedEndpoint = Store.get(StoreKey.serverEndpoint);
|
|
||||||
final Logger logger = Logger('UploadRepository');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final fileStream = file.openRead();
|
|
||||||
final assetRawUploadData = MultipartFile("assetData", fileStream, file.lengthSync(), filename: originalFileName);
|
|
||||||
|
|
||||||
final baseRequest = CustomMultipartRequest('POST', Uri.parse('$savedEndpoint/assets'), onProgress: onProgress);
|
|
||||||
|
|
||||||
baseRequest.headers.addAll(headers);
|
|
||||||
baseRequest.fields.addAll(fields);
|
|
||||||
baseRequest.files.add(assetRawUploadData);
|
|
||||||
|
|
||||||
final response = await httpClient.send(baseRequest, cancellationToken: cancelToken);
|
|
||||||
final responseBody = jsonDecode(await response.stream.bytesToString());
|
|
||||||
|
|
||||||
if (![200, 201].contains(response.statusCode)) {
|
|
||||||
final error = responseBody;
|
|
||||||
final errorMessage = error['message'] ?? error['error'];
|
|
||||||
|
|
||||||
logger.warning("Error(${error['statusCode']}) uploading $logContext | $originalFileName | ${error['error']}");
|
|
||||||
|
|
||||||
return UploadResult.error(statusCode: response.statusCode, errorMessage: errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return UploadResult.success(remoteAssetId: responseBody['id'] as String);
|
|
||||||
} on CancelledException {
|
|
||||||
logger.warning("Upload $logContext was cancelled");
|
|
||||||
return UploadResult.cancelled();
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
logger.warning("Error uploading $logContext: ${error.toString()}: $stackTrace");
|
|
||||||
return UploadResult.error(errorMessage: error.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result of an upload operation
|
|
||||||
class UploadResult {
|
|
||||||
final bool isSuccess;
|
|
||||||
final bool isCancelled;
|
|
||||||
final String? remoteAssetId;
|
|
||||||
final String? errorMessage;
|
|
||||||
final int? statusCode;
|
|
||||||
|
|
||||||
const UploadResult({
|
|
||||||
required this.isSuccess,
|
|
||||||
required this.isCancelled,
|
|
||||||
this.remoteAssetId,
|
|
||||||
this.errorMessage,
|
|
||||||
this.statusCode,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory UploadResult.success({required String remoteAssetId}) {
|
|
||||||
return UploadResult(isSuccess: true, isCancelled: false, remoteAssetId: remoteAssetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory UploadResult.error({String? errorMessage, int? statusCode}) {
|
|
||||||
return UploadResult(isSuccess: false, isCancelled: false, errorMessage: errorMessage, statusCode: statusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory UploadResult.cancelled() {
|
|
||||||
return const UploadResult(isSuccess: false, isCancelled: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Custom MultipartRequest with progress tracking
|
|
||||||
class CustomMultipartRequest extends MultipartRequest {
|
|
||||||
CustomMultipartRequest(super.method, super.url, {required this.onProgress});
|
|
||||||
|
|
||||||
final void Function(int bytes, int totalBytes) onProgress;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ByteStream finalize() {
|
|
||||||
final byteStream = super.finalize();
|
|
||||||
final total = contentLength;
|
|
||||||
var bytes = 0;
|
|
||||||
|
|
||||||
final t = StreamTransformer.fromHandlers(
|
|
||||||
handleData: (List<int> data, EventSink<List<int>> sink) {
|
|
||||||
bytes += data.length;
|
|
||||||
onProgress.call(bytes, total);
|
|
||||||
sink.add(data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
final stream = byteStream.transform(t);
|
|
||||||
return ByteStream(stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ import 'package:cancellation_token_http/http.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/local_asset_upload.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/upload.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
@@ -25,7 +28,6 @@ import 'package:immich_mobile/services/app_settings.service.dart';
|
|||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
|
|
||||||
|
|
||||||
final uploadServiceProvider = Provider((ref) {
|
final uploadServiceProvider = Provider((ref) {
|
||||||
final service = UploadService(
|
final service = UploadService(
|
||||||
@@ -35,6 +37,7 @@ final uploadServiceProvider = Provider((ref) {
|
|||||||
ref.watch(localAssetRepository),
|
ref.watch(localAssetRepository),
|
||||||
ref.watch(appSettingsServiceProvider),
|
ref.watch(appSettingsServiceProvider),
|
||||||
ref.watch(assetMediaRepositoryProvider),
|
ref.watch(assetMediaRepositoryProvider),
|
||||||
|
ref.watch(assetUploadRepositoryProvider),
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.onDispose(service.dispose);
|
ref.onDispose(service.dispose);
|
||||||
@@ -49,6 +52,7 @@ class UploadService {
|
|||||||
this._localAssetRepository,
|
this._localAssetRepository,
|
||||||
this._appSettingsService,
|
this._appSettingsService,
|
||||||
this._assetMediaRepository,
|
this._assetMediaRepository,
|
||||||
|
this._assetUploadRepository,
|
||||||
) {
|
) {
|
||||||
_uploadRepository.onUploadStatus = _onUploadCallback;
|
_uploadRepository.onUploadStatus = _onUploadCallback;
|
||||||
_uploadRepository.onTaskProgress = _onTaskProgressCallback;
|
_uploadRepository.onTaskProgress = _onTaskProgressCallback;
|
||||||
@@ -60,6 +64,7 @@ class UploadService {
|
|||||||
final DriftLocalAssetRepository _localAssetRepository;
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
final AppSettingsService _appSettingsService;
|
final AppSettingsService _appSettingsService;
|
||||||
final AssetMediaRepository _assetMediaRepository;
|
final AssetMediaRepository _assetMediaRepository;
|
||||||
|
final DriftLocalAssetUploadRepository _assetUploadRepository;
|
||||||
final Logger _logger = Logger('UploadService');
|
final Logger _logger = Logger('UploadService');
|
||||||
|
|
||||||
final StreamController<TaskStatusUpdate> _taskStatusController = StreamController<TaskStatusUpdate>.broadcast();
|
final StreamController<TaskStatusUpdate> _taskStatusController = StreamController<TaskStatusUpdate>.broadcast();
|
||||||
@@ -88,6 +93,14 @@ class UploadService {
|
|||||||
_taskProgressController.close();
|
_taskProgressController.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateError(String assetId, UploadErrorType errorType, String error) {
|
||||||
|
return _assetUploadRepository.upsert(assetId, errorType, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearError(String assetId) {
|
||||||
|
return _assetUploadRepository.delete(assetId);
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<bool>> enqueueTasks(List<UploadTask> tasks) {
|
Future<List<bool>> enqueueTasks(List<UploadTask> tasks) {
|
||||||
return _uploadRepository.enqueueBackgroundAll(tasks);
|
return _uploadRepository.enqueueBackgroundAll(tasks);
|
||||||
}
|
}
|
||||||
@@ -100,6 +113,15 @@ class UploadService {
|
|||||||
return _backupRepository.getAllCounts(userId);
|
return _backupRepository.getAllCounts(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> manualBackupId(String localId) async {
|
||||||
|
final localAsset = await _localAssetRepository.get(localId);
|
||||||
|
if (localAsset == null) {
|
||||||
|
_logger.warning('Local asset with id $localId not found for manual backup');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await manualBackup([localAsset]);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> manualBackup(List<LocalAsset> localAssets) async {
|
Future<void> manualBackup(List<LocalAsset> localAssets) async {
|
||||||
await _storageRepository.clearCache();
|
await _storageRepository.clearCache();
|
||||||
List<UploadTask> tasks = [];
|
List<UploadTask> tasks = [];
|
||||||
@@ -122,47 +144,41 @@ class UploadService {
|
|||||||
/// Find backup candidates
|
/// Find backup candidates
|
||||||
/// Build the upload tasks
|
/// Build the upload tasks
|
||||||
/// Enqueue the tasks
|
/// Enqueue the tasks
|
||||||
Future<void> startBackup(String userId, void Function(EnqueueStatus status) onEnqueueTasks) async {
|
Future<void> startBackup(
|
||||||
|
String userId,
|
||||||
|
void Function(EnqueueStatus status) onEnqueueTasks, {
|
||||||
|
bool ignoreFailed = false,
|
||||||
|
}) async {
|
||||||
await _storageRepository.clearCache();
|
await _storageRepository.clearCache();
|
||||||
|
await _assetUploadRepository.prune();
|
||||||
|
|
||||||
shouldAbortQueuingTasks = false;
|
shouldAbortQueuingTasks = false;
|
||||||
|
|
||||||
final candidates = await _backupRepository.getCandidates(userId);
|
final candidates = await _backupRepository.getCandidates(
|
||||||
|
userId,
|
||||||
|
limit: 100,
|
||||||
|
ignoreFailed: ignoreFailed,
|
||||||
|
sortBy: SortCandidatesBy.attemptCount,
|
||||||
|
);
|
||||||
if (candidates.isEmpty) {
|
if (candidates.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const batchSize = 100;
|
final tasks = (await Future.wait(candidates.map((asset) => getUploadTask(asset)))).nonNulls.toList();
|
||||||
int count = 0;
|
if (tasks.isNotEmpty && !shouldAbortQueuingTasks) {
|
||||||
for (int i = 0; i < candidates.length; i += batchSize) {
|
await enqueueTasks(tasks);
|
||||||
if (shouldAbortQueuingTasks) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
final batch = candidates.skip(i).take(batchSize).toList();
|
onEnqueueTasks(EnqueueStatus(enqueueCount: tasks.length, totalCount: candidates.length));
|
||||||
List<UploadTask> tasks = [];
|
|
||||||
for (final asset in batch) {
|
|
||||||
final task = await getUploadTask(asset);
|
|
||||||
if (task != null) {
|
|
||||||
tasks.add(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tasks.isNotEmpty && !shouldAbortQueuingTasks) {
|
|
||||||
count += tasks.length;
|
|
||||||
await enqueueTasks(tasks);
|
|
||||||
|
|
||||||
onEnqueueTasks(EnqueueStatus(enqueueCount: count, totalCount: candidates.length));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startBackupWithHttpClient(String userId, bool hasWifi, CancellationToken token) async {
|
Future<void> startBackupWithHttpClient(String userId, bool hasWifi, CancellationToken token) async {
|
||||||
await _storageRepository.clearCache();
|
await _storageRepository.clearCache();
|
||||||
|
await _assetUploadRepository.prune();
|
||||||
|
|
||||||
shouldAbortQueuingTasks = false;
|
shouldAbortQueuingTasks = false;
|
||||||
|
|
||||||
final candidates = await _backupRepository.getCandidates(userId);
|
final candidates = await _backupRepository.getCandidates(userId, sortBy: SortCandidatesBy.attemptCount);
|
||||||
if (candidates.isEmpty) {
|
if (candidates.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -194,208 +210,6 @@ class UploadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startForegroundUpload(
|
|
||||||
String userId,
|
|
||||||
CancellationToken cancelToken,
|
|
||||||
void Function(String localAssetId, int bytes, int totalBytes) onProgress,
|
|
||||||
void Function(String localAssetId, String remoteAssetId) onSuccess,
|
|
||||||
void Function(String errorMessage) onError, {
|
|
||||||
void Function(String localAssetId, double progress)? onICloudProgress,
|
|
||||||
}) async {
|
|
||||||
const concurrentUploads = 3;
|
|
||||||
final httpClients = List.generate(concurrentUploads, (_) => Client());
|
|
||||||
|
|
||||||
await _storageRepository.clearCache();
|
|
||||||
|
|
||||||
shouldAbortQueuingTasks = false;
|
|
||||||
|
|
||||||
final candidates = await _backupRepository.getCandidates(userId);
|
|
||||||
if (candidates.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
int clientIndex = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < candidates.length; i += concurrentUploads) {
|
|
||||||
if (shouldAbortQueuingTasks || cancelToken.isCancelled) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
final batch = candidates.skip(i).take(concurrentUploads).toList();
|
|
||||||
final uploadFutures = <Future<void>>[];
|
|
||||||
|
|
||||||
for (final asset in batch) {
|
|
||||||
final httpClient = httpClients[clientIndex % concurrentUploads];
|
|
||||||
clientIndex++;
|
|
||||||
|
|
||||||
uploadFutures.add(
|
|
||||||
_uploadSingleAsset(
|
|
||||||
asset,
|
|
||||||
httpClient,
|
|
||||||
cancelToken,
|
|
||||||
(bytes, totalBytes) => onProgress(asset.localId!, bytes, totalBytes),
|
|
||||||
onSuccess,
|
|
||||||
onError,
|
|
||||||
onICloudProgress: onICloudProgress,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Future.wait(uploadFutures);
|
|
||||||
|
|
||||||
if (shouldAbortQueuingTasks) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
for (final client in httpClients) {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _uploadSingleAsset(
|
|
||||||
LocalAsset asset,
|
|
||||||
Client httpClient,
|
|
||||||
CancellationToken cancelToken,
|
|
||||||
void Function(int bytes, int totalBytes) onProgress,
|
|
||||||
void Function(String localAssetId, String remoteAssetId) onSuccess,
|
|
||||||
void Function(String errorMessage) onError, {
|
|
||||||
void Function(String localAssetId, double progress)? onICloudProgress,
|
|
||||||
}) async {
|
|
||||||
File? file;
|
|
||||||
File? livePhotoFile;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final entity = await _storageRepository.getAssetEntityForAsset(asset);
|
|
||||||
if (entity == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final isAvailableLocally = await _storageRepository.isAssetAvailableLocally(asset.id);
|
|
||||||
|
|
||||||
if (!isAvailableLocally && Platform.isIOS) {
|
|
||||||
_logger.info("Loading iCloud asset ${asset.id} - ${asset.name}");
|
|
||||||
|
|
||||||
// Create progress handler for iCloud download
|
|
||||||
PMProgressHandler? progressHandler;
|
|
||||||
StreamSubscription? progressSubscription;
|
|
||||||
|
|
||||||
if (onICloudProgress != null) {
|
|
||||||
progressHandler = PMProgressHandler();
|
|
||||||
progressSubscription = progressHandler.stream.listen((event) {
|
|
||||||
onICloudProgress(asset.localId!, event.progress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
file = await _storageRepository.loadFileFromCloud(asset.id, progressHandler: progressHandler);
|
|
||||||
if (entity.isLivePhoto) {
|
|
||||||
livePhotoFile = await _storageRepository.loadMotionFileFromCloud(
|
|
||||||
asset.id,
|
|
||||||
progressHandler: progressHandler,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await progressSubscription?.cancel();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Get files locally
|
|
||||||
file = await _storageRepository.getFileForAsset(asset.id);
|
|
||||||
if (file == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For live photos, get the motion video file
|
|
||||||
if (entity.isLivePhoto) {
|
|
||||||
livePhotoFile = await _storageRepository.getMotionFileForAsset(asset);
|
|
||||||
if (livePhotoFile == null) {
|
|
||||||
_logger.warning("Failed to obtain motion part of the livePhoto - ${asset.name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file == null) {
|
|
||||||
_logger.warning("Failed to obtain file for asset ${asset.id} - ${asset.name}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final originalFileName = entity.isLivePhoto ? p.setExtension(asset.name, p.extension(file.path)) : asset.name;
|
|
||||||
final deviceId = Store.get(StoreKey.deviceId);
|
|
||||||
|
|
||||||
final headers = ApiService.getRequestHeaders();
|
|
||||||
final fields = {
|
|
||||||
'deviceAssetId': asset.localId!,
|
|
||||||
'deviceId': deviceId,
|
|
||||||
'fileCreatedAt': asset.createdAt.toUtc().toIso8601String(),
|
|
||||||
'fileModifiedAt': asset.updatedAt.toUtc().toIso8601String(),
|
|
||||||
'isFavorite': asset.isFavorite.toString(),
|
|
||||||
'duration': asset.duration.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Upload live photo video first if available
|
|
||||||
String? livePhotoVideoId;
|
|
||||||
if (entity.isLivePhoto && livePhotoFile != null) {
|
|
||||||
final livePhotoTitle = p.setExtension(originalFileName, p.extension(livePhotoFile.path));
|
|
||||||
livePhotoVideoId = await _uploadRepository.uploadLivePhotoVideo(
|
|
||||||
livePhotoFile: livePhotoFile,
|
|
||||||
originalFileName: livePhotoTitle,
|
|
||||||
headers: headers,
|
|
||||||
fields: fields,
|
|
||||||
httpClient: httpClient,
|
|
||||||
cancelToken: cancelToken,
|
|
||||||
onProgress: onProgress,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add livePhotoVideoId to fields if available
|
|
||||||
if (livePhotoVideoId != null) {
|
|
||||||
fields['livePhotoVideoId'] = livePhotoVideoId;
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await _uploadRepository.uploadSingleAsset(
|
|
||||||
file: file,
|
|
||||||
originalFileName: originalFileName,
|
|
||||||
headers: headers,
|
|
||||||
fields: fields,
|
|
||||||
httpClient: httpClient,
|
|
||||||
cancelToken: cancelToken,
|
|
||||||
onProgress: onProgress,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.isSuccess && result.remoteAssetId != null) {
|
|
||||||
onSuccess(asset.localId!, result.remoteAssetId!);
|
|
||||||
} else if (result.isCancelled) {
|
|
||||||
dPrint(() => "Backup was cancelled by the user");
|
|
||||||
shouldAbortQueuingTasks = true;
|
|
||||||
} else if (result.errorMessage != null) {
|
|
||||||
dPrint(
|
|
||||||
() =>
|
|
||||||
"Error(${result.statusCode}) uploading ${asset.localId} | $originalFileName | Created on ${asset.createdAt} | ${result.errorMessage}",
|
|
||||||
);
|
|
||||||
|
|
||||||
onError(result.errorMessage!);
|
|
||||||
|
|
||||||
if (result.errorMessage == "Quota has been exceeded!") {
|
|
||||||
shouldAbortQueuingTasks = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error, stackTrace) {
|
|
||||||
dPrint(() => "Error backup asset: ${error.toString()}: $stackTrace");
|
|
||||||
onError(error.toString());
|
|
||||||
} finally {
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
try {
|
|
||||||
await file?.delete();
|
|
||||||
await livePhotoFile?.delete();
|
|
||||||
} catch (e) {
|
|
||||||
dPrint(() => "ERROR deleting file: ${e.toString()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cancel all ongoing uploads and reset the upload queue
|
/// Cancel all ongoing uploads and reset the upload queue
|
||||||
///
|
///
|
||||||
/// Return the number of left over tasks in the queue
|
/// Return the number of left over tasks in the queue
|
||||||
@@ -403,6 +217,7 @@ class UploadService {
|
|||||||
shouldAbortQueuingTasks = true;
|
shouldAbortQueuingTasks = true;
|
||||||
|
|
||||||
await _storageRepository.clearCache();
|
await _storageRepository.clearCache();
|
||||||
|
await _assetUploadRepository.prune();
|
||||||
await _uploadRepository.reset(kBackupGroup);
|
await _uploadRepository.reset(kBackupGroup);
|
||||||
await _uploadRepository.deleteDatabaseRecords(kBackupGroup);
|
await _uploadRepository.deleteDatabaseRecords(kBackupGroup);
|
||||||
|
|
||||||
|
|||||||
5
mobile/test/drift/main/generated/schema.dart
generated
5
mobile/test/drift/main/generated/schema.dart
generated
@@ -15,6 +15,7 @@ import 'schema_v9.dart' as v9;
|
|||||||
import 'schema_v10.dart' as v10;
|
import 'schema_v10.dart' as v10;
|
||||||
import 'schema_v11.dart' as v11;
|
import 'schema_v11.dart' as v11;
|
||||||
import 'schema_v12.dart' as v12;
|
import 'schema_v12.dart' as v12;
|
||||||
|
import 'schema_v13.dart' as v13;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -44,10 +45,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v11.DatabaseAtV11(db);
|
return v11.DatabaseAtV11(db);
|
||||||
case 12:
|
case 12:
|
||||||
return v12.DatabaseAtV12(db);
|
return v12.DatabaseAtV12(db);
|
||||||
|
case 13:
|
||||||
|
return v13.DatabaseAtV13(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
||||||
}
|
}
|
||||||
|
|||||||
7506
mobile/test/drift/main/generated/schema_v13.dart
generated
Normal file
7506
mobile/test/drift/main/generated/schema_v13.dart
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,19 @@
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset_upload.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/album_media.repository.dart';
|
import 'package:immich_mobile/repositories/album.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/album_api.repository.dart';
|
import 'package:immich_mobile/repositories/album_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/partner.repository.dart';
|
import 'package:immich_mobile/repositories/album_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/etag.repository.dart';
|
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/backup.repository.dart';
|
|
||||||
import 'package:immich_mobile/repositories/auth.repository.dart';
|
import 'package:immich_mobile/repositories/auth.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
import 'package:immich_mobile/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/etag.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/album.repository.dart';
|
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
|
import 'package:immich_mobile/repositories/partner.repository.dart';
|
||||||
|
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class MockAlbumRepository extends Mock implements AlbumRepository {}
|
class MockAlbumRepository extends Mock implements AlbumRepository {}
|
||||||
@@ -46,3 +47,5 @@ class MockPartnerRepository extends Mock implements PartnerRepository {}
|
|||||||
class MockPartnerApiRepository extends Mock implements PartnerApiRepository {}
|
class MockPartnerApiRepository extends Mock implements PartnerApiRepository {}
|
||||||
|
|
||||||
class MockLocalFilesManagerRepository extends Mock implements LocalFilesManagerRepository {}
|
class MockLocalFilesManagerRepository extends Mock implements LocalFilesManagerRepository {}
|
||||||
|
|
||||||
|
class MockLocalAssetUploadRepository extends Mock implements DriftLocalAssetUploadRepository {}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ void main() {
|
|||||||
late MockDriftLocalAssetRepository mockLocalAssetRepository;
|
late MockDriftLocalAssetRepository mockLocalAssetRepository;
|
||||||
late MockAppSettingsService mockAppSettingsService;
|
late MockAppSettingsService mockAppSettingsService;
|
||||||
late MockAssetMediaRepository mockAssetMediaRepository;
|
late MockAssetMediaRepository mockAssetMediaRepository;
|
||||||
|
late MockLocalAssetUploadRepository mockLocalAssetUploadRepository;
|
||||||
late Drift db;
|
late Drift db;
|
||||||
|
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
@@ -53,6 +54,7 @@ void main() {
|
|||||||
mockLocalAssetRepository = MockDriftLocalAssetRepository();
|
mockLocalAssetRepository = MockDriftLocalAssetRepository();
|
||||||
mockAppSettingsService = MockAppSettingsService();
|
mockAppSettingsService = MockAppSettingsService();
|
||||||
mockAssetMediaRepository = MockAssetMediaRepository();
|
mockAssetMediaRepository = MockAssetMediaRepository();
|
||||||
|
mockLocalAssetUploadRepository = MockLocalAssetUploadRepository();
|
||||||
|
|
||||||
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false);
|
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false);
|
||||||
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false);
|
when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false);
|
||||||
@@ -64,6 +66,7 @@ void main() {
|
|||||||
mockLocalAssetRepository,
|
mockLocalAssetRepository,
|
||||||
mockAppSettingsService,
|
mockAppSettingsService,
|
||||||
mockAssetMediaRepository,
|
mockAssetMediaRepository,
|
||||||
|
mockLocalAssetUploadRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
mockUploadRepository.onUploadStatus = (_) {};
|
mockUploadRepository.onUploadStatus = (_) {};
|
||||||
|
|||||||
Reference in New Issue
Block a user