Fix backup loop, NAS Archive regression, and manifest validity check
- AutoBackupCoordinator: add 55s debounce to onActive() so repeated .task and scenePhase triggers (navigation appear, rapid foreground cycles) are no-ops; start a 60s periodic recheck timer on first active call; stop timer and reset debounce on willResignActiveNotification so each new foreground session always gets an immediate fresh status check - BackupView: remove the redundant lanMonitor.nasReachable onChange that was calling statusService.refresh() directly — coordinator already handles this via its Combine $nasReachable subscriber, avoiding a double reconcile that could bypass the autoBackupOnOpen gate with the wrong lastTriggerWasLAN value - BackupStatusService: when NAS connect fails, restore nasArchiveTotal from NASManifestCache before writing the offline snapshot — prevents the count from showing 0 when the NAS is temporarily unreachable but the cache is warm - BackupStatusService: manifest validity check — if the decoded manifest has ≤ 1 entries (corrupt or newly created), scan the NAS directory and use the real file count as the display floor for nasArchiveTotal without adding orphan entries to the manifest (that was the 7421 regression) - BackupManifest: add ManifestIndex.init(manifest:overrideTotalCount:) for the validity check path — keeps localIdentifier matching intact while correcting the displayed archive count - SMBService: add os.log around connect/auth/listDirectory with actual error reason instead of always surfacing authenticationFailed; distinguish network errors (timeout, host unreachable) from auth errors in thrown BackupError Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import Foundation
|
||||
import SMBClient
|
||||
import os.log
|
||||
|
||||
private let log = Logger(subsystem: "com.albert.nasbackup", category: "SMBService")
|
||||
|
||||
final class SMBService: NASTransferProtocol {
|
||||
private var client: SMBClient?
|
||||
@@ -16,12 +19,24 @@ final class SMBService: NASTransferProtocol {
|
||||
}
|
||||
|
||||
func connect(to host: String, port: Int, username: String, password: String) async throws {
|
||||
log.info("SMB connecting — host=\(host, privacy: .public):\(port) user=\(username, privacy: .private)")
|
||||
let c = SMBClient(host: host, port: port)
|
||||
do {
|
||||
try await c.login(username: username, password: password)
|
||||
} catch {
|
||||
log.error("SMB connect failed — host=\(host, privacy: .public):\(port) error=\(error.localizedDescription, privacy: .public)")
|
||||
// Propagate a connection error rather than always claiming auth failed —
|
||||
// the real cause could be host unreachable, wrong port, or network timeout.
|
||||
let nsErr = error as NSError
|
||||
if nsErr.domain == NSURLErrorDomain ||
|
||||
nsErr.code == NSURLErrorTimedOut ||
|
||||
nsErr.code == NSURLErrorCannotConnectToHost ||
|
||||
nsErr.code == NSURLErrorNetworkConnectionLost {
|
||||
throw BackupError.connectionFailed("\(host):\(port) — \(error.localizedDescription)")
|
||||
}
|
||||
throw BackupError.authenticationFailed
|
||||
}
|
||||
log.info("SMB auth OK — \(host, privacy: .public):\(port)")
|
||||
self.client = c
|
||||
self.isConnected = true
|
||||
}
|
||||
@@ -47,9 +62,10 @@ final class SMBService: NASTransferProtocol {
|
||||
func listDirectory(at path: String) async throws -> [NASItem] {
|
||||
guard let client else { throw BackupError.connectionFailed("Not connected") }
|
||||
let (share, rel) = splitPath(path)
|
||||
log.info("SMB listDirectory — share=\(share, privacy: .public) path=\(rel.isEmpty ? "/" : rel, privacy: .public)")
|
||||
try await client.connectShare(share)
|
||||
let files = try await client.listDirectory(path: rel.isEmpty ? "" : rel)
|
||||
return files.compactMap { file -> NASItem? in
|
||||
let items = files.compactMap { file -> NASItem? in
|
||||
guard file.name != "." && file.name != ".." else { return nil }
|
||||
let fullPath = rel.isEmpty ? "/\(share)/\(file.name)" : "/\(share)/\(rel)/\(file.name)"
|
||||
return NASItem(
|
||||
@@ -60,6 +76,8 @@ final class SMBService: NASTransferProtocol {
|
||||
modifiedDate: file.lastWriteTime
|
||||
)
|
||||
}
|
||||
log.info("SMB listDirectory result — \(items.count) items at \(path, privacy: .public)")
|
||||
return items
|
||||
}
|
||||
|
||||
func createDirectory(at path: String) async throws {
|
||||
|
||||
Reference in New Issue
Block a user