mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
background upload plugin
add schemas sync variants formatting initial implementation use existing db, wip move to separate folder fix table definitions wip wiring it up
This commit is contained in:
@@ -1,70 +1,160 @@
|
||||
import GRDB
|
||||
import SQLiteData
|
||||
|
||||
extension QueryExpression where QueryValue: _OptionalProtocol {
|
||||
// asserts column result cannot be nil
|
||||
var unwrapped: SQLQueryExpression<QueryValue.Wrapped> {
|
||||
SQLQueryExpression(self.queryFragment, as: QueryValue.Wrapped.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Date {
|
||||
var unixTime: Date.UnixTimeRepresentation {
|
||||
return Date.UnixTimeRepresentation(queryOutput: self)
|
||||
}
|
||||
}
|
||||
|
||||
@Table("asset_face_entity")
|
||||
struct AssetFace {
|
||||
struct AssetFace: Identifiable {
|
||||
let id: String
|
||||
let assetId: String
|
||||
let personId: String?
|
||||
@Column("asset_id")
|
||||
let assetId: RemoteAsset.ID
|
||||
@Column("person_id")
|
||||
let personId: Person.ID?
|
||||
@Column("image_width")
|
||||
let imageWidth: Int
|
||||
@Column("image_height")
|
||||
let imageHeight: Int
|
||||
@Column("bounding_box_x1")
|
||||
let boundingBoxX1: Int
|
||||
@Column("bounding_box_y1")
|
||||
let boundingBoxY1: Int
|
||||
@Column("bounding_box_x2")
|
||||
let boundingBoxX2: Int
|
||||
@Column("bounding_box_y2")
|
||||
let boundingBoxY2: Int
|
||||
let sourceType: String
|
||||
@Column("source_type")
|
||||
let sourceType: SourceType
|
||||
}
|
||||
|
||||
@Table("auth_user_entity")
|
||||
struct AuthUser {
|
||||
struct AuthUser: Identifiable {
|
||||
let id: String
|
||||
let name: String
|
||||
let email: String
|
||||
@Column("is_admin")
|
||||
let isAdmin: Bool
|
||||
@Column("has_profile_image")
|
||||
let hasProfileImage: Bool
|
||||
@Column("profile_changed_at")
|
||||
let profileChangedAt: Date
|
||||
@Column("avatar_color")
|
||||
let avatarColor: AvatarColor
|
||||
@Column("quota_size_in_bytes")
|
||||
let quotaSizeInBytes: Int
|
||||
@Column("quota_usage_in_bytes")
|
||||
let quotaUsageInBytes: Int
|
||||
@Column("pin_code")
|
||||
let pinCode: String?
|
||||
}
|
||||
|
||||
@Table("local_album_entity")
|
||||
struct LocalAlbum {
|
||||
struct LocalAlbum: Identifiable {
|
||||
let id: String
|
||||
@Column("backup_selection")
|
||||
let backupSelection: BackupSelection
|
||||
let linkedRemoteAlbumId: String?
|
||||
@Column("linked_remote_album_id")
|
||||
let linkedRemoteAlbumId: RemoteAlbum.ID?
|
||||
@Column("marker")
|
||||
let marker_: Bool?
|
||||
let name: String
|
||||
@Column("is_ios_shared_album")
|
||||
let isIosSharedAlbum: Bool
|
||||
@Column("updated_at")
|
||||
let updatedAt: Date
|
||||
}
|
||||
|
||||
extension LocalAlbum {
|
||||
static let selected = Self.where { $0.backupSelection.eq(BackupSelection.selected) }
|
||||
static let excluded = Self.where { $0.backupSelection.eq(BackupSelection.excluded) }
|
||||
}
|
||||
|
||||
@Table("local_album_asset_entity")
|
||||
struct LocalAlbumAsset {
|
||||
let id: ID
|
||||
@Column("marker")
|
||||
let marker_: String?
|
||||
|
||||
@Selection
|
||||
struct ID {
|
||||
@Column("asset_id")
|
||||
let assetId: String
|
||||
@Column("album_id")
|
||||
let albumId: String
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalAlbumAsset {
|
||||
static let selected = Self.where {
|
||||
$0.id.assetId.eq(LocalAsset.columns.id) && $0.id.albumId.in(LocalAlbum.selected.select(\.id))
|
||||
}
|
||||
static let excluded = Self.where {
|
||||
$0.id.assetId.eq(LocalAsset.columns.id) && $0.id.albumId.in(LocalAlbum.excluded.select(\.id))
|
||||
}
|
||||
}
|
||||
|
||||
@Table("local_asset_entity")
|
||||
struct LocalAsset {
|
||||
struct LocalAsset: Identifiable {
|
||||
let id: String
|
||||
let checksum: String?
|
||||
let createdAt: Date
|
||||
@Column("created_at")
|
||||
let createdAt: String
|
||||
@Column("duration_in_seconds")
|
||||
let durationInSeconds: Int?
|
||||
let height: Int?
|
||||
@Column("is_favorite")
|
||||
let isFavorite: Bool
|
||||
let name: String
|
||||
let orientation: String
|
||||
let type: Int
|
||||
let updatedAt: Date
|
||||
let type: AssetType
|
||||
@Column("updated_at")
|
||||
let updatedAt: String
|
||||
let width: Int?
|
||||
|
||||
static func getCandidates() -> Where<LocalAsset> {
|
||||
return Self.where { local in
|
||||
LocalAlbumAsset.selected.exists()
|
||||
&& !LocalAlbumAsset.excluded.exists()
|
||||
&& !RemoteAsset.where {
|
||||
local.checksum.eq($0.checksum)
|
||||
&& $0.ownerId.eq(Store.select(\.stringValue).where { $0.id.eq(StoreKey.currentUser.rawValue) }.unwrapped)
|
||||
}.exists()
|
||||
&& !UploadTask.where { $0.localId.eq(local.id) }.exists()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Selection
|
||||
struct LocalAssetCandidate {
|
||||
let id: LocalAsset.ID
|
||||
let type: AssetType
|
||||
}
|
||||
|
||||
@Selection
|
||||
struct LocalAssetDownloadData {
|
||||
let checksum: String?
|
||||
let createdAt: String
|
||||
let livePhotoVideoId: RemoteAsset.ID?
|
||||
let localId: LocalAsset.ID
|
||||
let taskId: UploadTask.ID
|
||||
let updatedAt: String
|
||||
}
|
||||
|
||||
@Selection
|
||||
struct LocalAssetUploadData {
|
||||
let filePath: URL
|
||||
let priority: Float
|
||||
let taskId: UploadTask.ID
|
||||
let type: AssetType
|
||||
}
|
||||
|
||||
@Table("memory_asset_entity")
|
||||
@@ -73,63 +163,89 @@ struct MemoryAsset {
|
||||
|
||||
@Selection
|
||||
struct ID {
|
||||
@Column("asset_id")
|
||||
let assetId: String
|
||||
@Column("album_id")
|
||||
let albumId: String
|
||||
}
|
||||
}
|
||||
|
||||
@Table("memory_entity")
|
||||
struct Memory {
|
||||
struct Memory: Identifiable {
|
||||
let id: String
|
||||
@Column("created_at")
|
||||
let createdAt: Date
|
||||
@Column("updated_at")
|
||||
let updatedAt: Date
|
||||
@Column("deleted_at")
|
||||
let deletedAt: Date?
|
||||
let ownerId: String
|
||||
@Column("owner_id")
|
||||
let ownerId: User.ID
|
||||
let type: MemoryType
|
||||
let data: String
|
||||
@Column("is_saved")
|
||||
let isSaved: Bool
|
||||
@Column("memory_at")
|
||||
let memoryAt: Date
|
||||
@Column("seen_at")
|
||||
let seenAt: Date?
|
||||
@Column("show_at")
|
||||
let showAt: Date?
|
||||
@Column("hide_at")
|
||||
let hideAt: Date?
|
||||
}
|
||||
|
||||
@Table("partner_entity")
|
||||
struct Partner {
|
||||
let id: ID
|
||||
@Column("in_timeline")
|
||||
let inTimeline: Bool
|
||||
|
||||
@Selection
|
||||
struct ID {
|
||||
@Column("shared_by_id")
|
||||
let sharedById: String
|
||||
@Column("shared_with_id")
|
||||
let sharedWithId: String
|
||||
}
|
||||
}
|
||||
|
||||
@Table("person_entity")
|
||||
struct Person {
|
||||
struct Person: Identifiable {
|
||||
let id: String
|
||||
@Column("created_at")
|
||||
let createdAt: Date
|
||||
@Column("updated_at")
|
||||
let updatedAt: Date
|
||||
@Column("owner_id")
|
||||
let ownerId: String
|
||||
let name: String
|
||||
let faceAssetId: String?
|
||||
@Column("face_asset_id")
|
||||
let faceAssetId: AssetFace.ID?
|
||||
@Column("is_favorite")
|
||||
let isFavorite: Bool
|
||||
@Column("is_hidden")
|
||||
let isHidden: Bool
|
||||
let color: String?
|
||||
@Column("birth_date")
|
||||
let birthDate: Date?
|
||||
}
|
||||
|
||||
@Table("remote_album_entity")
|
||||
struct RemoteAlbum {
|
||||
struct RemoteAlbum: Identifiable {
|
||||
let id: String
|
||||
@Column("created_at")
|
||||
let createdAt: Date
|
||||
let description: String?
|
||||
@Column("is_activity_enabled")
|
||||
let isActivityEnabled: Bool
|
||||
let name: String
|
||||
let order: Int
|
||||
@Column("owner_id")
|
||||
let ownerId: String
|
||||
let thumbnailAssetId: String?
|
||||
@Column("thumbnail_asset_id")
|
||||
let thumbnailAssetId: RemoteAsset.ID?
|
||||
@Column("updated_at")
|
||||
let updatedAt: Date
|
||||
}
|
||||
|
||||
@@ -139,7 +255,9 @@ struct RemoteAlbumAsset {
|
||||
|
||||
@Selection
|
||||
struct ID {
|
||||
@Column("asset_id")
|
||||
let assetId: String
|
||||
@Column("album_id")
|
||||
let albumId: String
|
||||
}
|
||||
}
|
||||
@@ -151,40 +269,55 @@ struct RemoteAlbumUser {
|
||||
|
||||
@Selection
|
||||
struct ID {
|
||||
@Column("album_id")
|
||||
let albumId: String
|
||||
@Column("user_id")
|
||||
let userId: String
|
||||
}
|
||||
}
|
||||
|
||||
@Table("remote_asset_entity")
|
||||
struct RemoteAsset {
|
||||
struct RemoteAsset: Identifiable {
|
||||
let id: String
|
||||
let checksum: String?
|
||||
let checksum: String
|
||||
@Column("is_favorite")
|
||||
let isFavorite: Bool
|
||||
@Column("deleted_at")
|
||||
let deletedAt: Date?
|
||||
let isFavorite: Int
|
||||
let libraryId: String?
|
||||
let livePhotoVideoId: String?
|
||||
@Column("owner_id")
|
||||
let ownerId: User.ID
|
||||
@Column("local_date_time")
|
||||
let localDateTime: Date?
|
||||
let orientation: String
|
||||
let ownerId: String
|
||||
let stackId: String?
|
||||
let visibility: Int
|
||||
@Column("thumb_hash")
|
||||
let thumbHash: String?
|
||||
@Column("library_id")
|
||||
let libraryId: String?
|
||||
@Column("live_photo_video_id")
|
||||
let livePhotoVideoId: String?
|
||||
@Column("stack_id")
|
||||
let stackId: Stack.ID?
|
||||
let visibility: AssetVisibility
|
||||
}
|
||||
|
||||
@Table("remote_exif_entity")
|
||||
struct RemoteExif {
|
||||
@Column(primaryKey: true)
|
||||
let assetId: String
|
||||
@Column("asset_id", primaryKey: true)
|
||||
let assetId: RemoteAsset.ID
|
||||
let city: String?
|
||||
let state: String?
|
||||
let country: String?
|
||||
@Column("date_time_original")
|
||||
let dateTimeOriginal: Date?
|
||||
let description: String?
|
||||
let height: Int?
|
||||
let width: Int?
|
||||
@Column("exposure_time")
|
||||
let exposureTime: String?
|
||||
@Column("f_number")
|
||||
let fNumber: Double?
|
||||
@Column("file_size")
|
||||
let fileSize: Int?
|
||||
@Column("focal_length")
|
||||
let focalLength: Double?
|
||||
let latitude: Double?
|
||||
let longitude: Double?
|
||||
@@ -193,34 +326,110 @@ struct RemoteExif {
|
||||
let model: String?
|
||||
let lens: String?
|
||||
let orientation: String?
|
||||
@Column("time_zone")
|
||||
let timeZone: String?
|
||||
let rating: Int?
|
||||
@Column("projection_type")
|
||||
let projectionType: String?
|
||||
}
|
||||
|
||||
@Table("stack_entity")
|
||||
struct Stack {
|
||||
struct Stack: Identifiable {
|
||||
let id: String
|
||||
@Column("created_at")
|
||||
let createdAt: Date
|
||||
@Column("updated_at")
|
||||
let updatedAt: Date
|
||||
let ownerId: String
|
||||
@Column("owner_id")
|
||||
let ownerId: User.ID
|
||||
@Column("primary_asset_id")
|
||||
let primaryAssetId: String
|
||||
}
|
||||
|
||||
@Table("store_entity")
|
||||
struct Store {
|
||||
struct Store: Identifiable {
|
||||
let id: StoreKey
|
||||
@Column("string_value")
|
||||
let stringValue: String?
|
||||
@Column("int_value")
|
||||
let intValue: Int?
|
||||
}
|
||||
|
||||
@Table("upload_tasks")
|
||||
struct UploadTask: Identifiable {
|
||||
let id: Int64
|
||||
let attempts: Int
|
||||
@Column("created_at", as: Date.UnixTimeRepresentation.self)
|
||||
let createdAt: Date
|
||||
@Column("file_path")
|
||||
var filePath: URL?
|
||||
@Column("is_live_photo")
|
||||
let isLivePhoto: Bool?
|
||||
@Column("last_error")
|
||||
let lastError: UploadErrorCode?
|
||||
@Column("live_photo_video_id")
|
||||
let livePhotoVideoId: RemoteAsset.ID?
|
||||
@Column("local_id")
|
||||
var localId: LocalAsset.ID?
|
||||
let method: UploadMethod
|
||||
var priority: Float
|
||||
@Column("retry_after", as: Date?.UnixTimeRepresentation.self)
|
||||
let retryAfter: Date?
|
||||
let status: TaskStatus
|
||||
|
||||
static func retryOrFail(code: UploadErrorCode, status: TaskStatus) -> Update<UploadTask, ()> {
|
||||
return Self.update { row in
|
||||
row.status = Case().when(row.attempts.lte(TaskConfig.maxRetries), then: TaskStatus.downloadPending).else(status)
|
||||
row.attempts += 1
|
||||
row.lastError = code
|
||||
row.retryAfter = #sql("unixepoch('now') + (\(4 << row.attempts))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Table("upload_task_stats")
|
||||
struct UploadTaskStat {
|
||||
@Column("pending_downloads")
|
||||
let pendingDownloads: Int
|
||||
@Column("pending_uploads")
|
||||
let pendingUploads: Int
|
||||
@Column("queued_downloads")
|
||||
let queuedDownloads: Int
|
||||
@Column("queued_uploads")
|
||||
let queuedUploads: Int
|
||||
@Column("failed_downloads")
|
||||
let failedDownloads: Int
|
||||
@Column("failed_uploads")
|
||||
let failedUploads: Int
|
||||
@Column("completed_uploads")
|
||||
let completedUploads: Int
|
||||
@Column("skipped_uploads")
|
||||
let skippedUploads: Int
|
||||
|
||||
static let availableDownloadSlots = Self.select {
|
||||
TaskConfig.maxPendingDownloads - ($0.pendingDownloads + $0.queuedDownloads)
|
||||
}
|
||||
|
||||
static let availableUploadSlots = Self.select {
|
||||
TaskConfig.maxPendingUploads - ($0.pendingUploads + $0.queuedUploads)
|
||||
}
|
||||
|
||||
static let availableSlots = Self.select {
|
||||
TaskConfig.maxPendingUploads + TaskConfig.maxPendingDownloads
|
||||
- ($0.pendingDownloads + $0.queuedDownloads + $0.pendingUploads + $0.queuedUploads)
|
||||
}
|
||||
}
|
||||
|
||||
@Table("user_entity")
|
||||
struct User {
|
||||
struct User: Identifiable {
|
||||
let id: String
|
||||
let name: String
|
||||
let email: String
|
||||
@Column("has_profile_image")
|
||||
let hasProfileImage: Bool
|
||||
@Column("profile_changed_at")
|
||||
let profileChangedAt: Date
|
||||
@Column("avatar_color")
|
||||
let avatarColor: AvatarColor
|
||||
}
|
||||
|
||||
@@ -231,6 +440,7 @@ struct UserMetadata {
|
||||
|
||||
@Selection
|
||||
struct ID {
|
||||
@Column("user_id")
|
||||
let userId: String
|
||||
let key: Date
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user