-
Notifications
You must be signed in to change notification settings - Fork 22
feat: Add WireCellsLocalAssetRepository
- WPB-18848
#3400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Test Results62 tests 62 ✅ 12s ⏱️ Results for commit 10ce4b6. ♻️ This comment has been updated with latest results. |
/// The size of the asset in bytes as defined by the backend or `-1` if unknown. | ||
|
||
public let size: UInt64? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: if -1 as default, does it need to be optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh the comment is wrong here. When it gets stored in core data it will be -1. In the domain it will be optional. I will fix it. Thanks!
public var cacheKey: String { | ||
"\(nodeID.uuidString)-\(eTag)" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: is it data layer or domain layer property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that whole object should just be data layer. I will move it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: usually I'm against DTO in the name because it's not clear to which layer it belongs and what it maps form or to?
I'd suggest some other prefix+model, e.g. StoredModel, CachedModel, NetworkModel, etc.
Is it possible to find better name for this model?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. This is still left over from the original implementation and I haven't got around to changing it yet. It should be the NetworkModel
I believe. I'll fix it. Eventually it will move somewhere else.
|
||
final class WireCellsTests: XCTestCase {} | ||
// sourcery: AutoMockable | ||
public protocol FileCache: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: FileCache is about persistence but lives under WireCellsAPI which is network related code and FileCache is more like related to file system? Probably better put to some other folder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI WireCellsAPI
is not network related code - it's currently a big mix. It used to be a way of the different layers talking to each other. Julian is working on moving existing code from WireCells to WireMessaging after which we will follow our clean architecture approach. I will will move this to WireMessagingData
once his PR is done.
WireCells/Sources/WireCellsAPI/Persistence/WireCellsLocalAssetMetadataStore.swift
Show resolved
Hide resolved
switch downloadStates[nodeID] { | ||
case .downloading: | ||
throw WireCellsLocalAssetRepositoryError.downloadAlreadyInProgress | ||
case .error: | ||
setDownloadState(nodeID: nodeID, state: .none) | ||
case .none: | ||
break | ||
} | ||
|
||
do { | ||
var (node, metadata) = try await _refreshMetadata(nodeID: nodeID) | ||
let (downloadURL, eTag) = try node.downloadInfo | ||
|
||
let (progress, download) = fileDownloader.download(from: downloadURL) | ||
|
||
for await progress in progress { | ||
setDownloadState(nodeID: nodeID, state: .downloading(progress: progress, task: download)) | ||
} | ||
|
||
let (tempURL, _) = try await download.value | ||
try await fileCache.saveFile(at: tempURL, key: metadata.cacheKey) | ||
|
||
if try metadataStore.assetMetadata(nodeID: nodeID)?.eTag == eTag { | ||
// downloaded file is up-to-date so update the metadata | ||
metadata.isDownloaded = true | ||
try metadataStore.upsertAssetMetadata(metadata) | ||
} else { | ||
// remove the out-of-date file | ||
try await fileCache.deleteFile(forKey: metadata.cacheKey) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: To me it looks like more of a use case not a repo, with business logic with states and api, file cache and metadata store as individual repositories, wdyt @samwyndham ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So making the repositories simple right and moving the logic to the use case. I will have a think about it. It might well make more sense.
|
||
public let downloadState: DownloadState | ||
|
||
public init( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: will a public init be needed?
|
||
case failed(error: any Error) | ||
|
||
public static func == (lhs: DownloadState, rhs: DownloadState) -> Bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Do we really need Equatable
conformance?
If we want to have it, I'm not sure that the current implementation, ignoring the actual error type, is behaving like any user would expect.
optional: One alternative I could think of is offering properties, like isPending: Bool
etc.
But there are other possibilities, depending on what the goal actually is.
public import Foundation | ||
|
||
// sourcery: AutoMockable | ||
@MainActor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: is it really needed to restrict every conforming type to be isolated to the @MainActor
?
What about moving the attribute to the funcs?
import Foundation | ||
public import WireCellsAPI | ||
|
||
extension URLSession: FileDownloading { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personal opinion: Declaring protocols and making third party (or SDK) types conform to one's own protocols is an anti-pattern for me. As usual I'm having difficulties phrasing my thoughts why it feels wrong.
But one example what I don't like is that from now on every target which links WireCells suddenly offers a URLSession.download(from:)
method.
Depending on whether we want this code to be a general helper for any other part of the codebase, or whether this is something WireCells specific, we should either move this into WireUtilities/WireFoundation or prevent the namespace of URLSession
from being spoiled with this API.
I suggest to make a simple structure wrapping an URLSession and conforming to the FileDownloading
protocol.
} | ||
|
||
func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { | ||
precondition(cancellables.isEmpty, "Delegate must not be reused across multiple tasks.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: does it mean if one wants to download two files the following will have to be done in a strictly sequential order?
- set URLSession delegate to a new instance of
URLSessionTaskProgressDelegate
- start a task and wait for it to be finished
- replace the delegate of the URLSession
- start another task
import Foundation | ||
import WireCellsAPI | ||
|
||
/// Repository for accessing & updating `WireCellsLocalAsset`s. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Repository for accessing & updating `WireCellsLocalAsset`s. | |
/// Repository for accessing & updating `WireCellsLocalAssets. |
Since you're the native speaker feel free to ignore this.
Issue
This PR introduces a
WireCellsLocalAssetRepository
. This repository is responsible for downloading and accessing files from wire cells. AWireCellsLocalAsset
holds metadata about an asset (id, size, etc) and what its current download state is. The repository provides a way of observing these assets for a given ID. This will allow table view cells in the conversation to have live updates as things change - for example showing a download progress bar or updating if the name of a file is changed remotely.Currently I'm not super happy about the implementation and may revisit this in a little when I come to using it in the project. However, the public APIs of
WireCellsLocalAssetRepository
andWireCellsLocalAsset
are how I imagine they should be.Testing
Run automated tests
Checklist
[WPB-XXX]
.