@@ -50,6 +50,8 @@ public class RepositoryManager: Cancellable {
50
50
// tracks outstanding lookups for cancellation
51
51
private var outstandingLookups = ThreadSafeKeyValueStore < UUID , ( repository: RepositorySpecifier , completion: ( Result < RepositoryHandle , Error > ) -> Void , queue: DispatchQueue ) > ( )
52
52
53
+ private var emitNoConnectivityWarning = ThreadSafeBox < Bool > ( true )
54
+
53
55
/// Create a new empty manager.
54
56
///
55
57
/// - Parameters:
@@ -325,12 +327,27 @@ public class RepositoryManager: Cancellable {
325
327
}
326
328
}
327
329
} catch {
328
- cacheUsed = false
329
- // Fetch without populating the cache in the case of an error.
330
- observabilityScope. emit ( warning: " skipping cache due to an error: \( error) " )
331
- // it is possible that we already created the directory from failed attempts, so clear leftover data if present.
332
- try ? self . fileSystem. removeFileTree ( repositoryPath)
333
- try self . provider. fetch ( repository: handle. repository, to: repositoryPath, progressHandler: updateFetchProgress ( progress: ) )
330
+ // If we are offline and have a valid cached repository, use the cache anyway.
331
+ if isOffline ( error) && self . provider. isValidDirectory ( cachedRepositoryPath) {
332
+ // For the first offline use in the lifetime of this repository manager, emit a warning.
333
+ if self . emitNoConnectivityWarning. get ( default: false ) {
334
+ self . emitNoConnectivityWarning. put ( false )
335
+ observabilityScope. emit ( warning: " no connectivity, using previously cached repository state " )
336
+ }
337
+ observabilityScope. emit ( info: " using previously cached repository state for \( package ) " )
338
+
339
+ cacheUsed = true
340
+ // Copy the repository from the cache into the repository path.
341
+ try self . fileSystem. createDirectory ( repositoryPath. parentDirectory, recursive: true )
342
+ try self . provider. copy ( from: cachedRepositoryPath, to: repositoryPath)
343
+ } else {
344
+ cacheUsed = false
345
+ // Fetch without populating the cache in the case of an error.
346
+ observabilityScope. emit ( warning: " skipping cache due to an error: \( error) " )
347
+ // it is possible that we already created the directory from failed attempts, so clear leftover data if present.
348
+ try ? self . fileSystem. removeFileTree ( repositoryPath)
349
+ try self . provider. fetch ( repository: handle. repository, to: repositoryPath, progressHandler: updateFetchProgress ( progress: ) )
350
+ }
334
351
}
335
352
} else {
336
353
// it is possible that we already created the directory from failed attempts, so clear leftover data if present.
@@ -513,3 +530,45 @@ extension RepositorySpecifier {
513
530
}
514
531
}
515
532
533
+ #if canImport(SystemConfiguration)
534
+ import SystemConfiguration
535
+
536
+ private struct Reachability {
537
+ let reachability : SCNetworkReachability
538
+
539
+ init ? ( ) {
540
+ var emptyAddress = sockaddr ( )
541
+ emptyAddress. sa_len = UInt8 ( MemoryLayout< sockaddr> . size)
542
+ emptyAddress. sa_family = sa_family_t ( AF_INET)
543
+
544
+ guard let reachability = withUnsafePointer ( to: & emptyAddress, {
545
+ SCNetworkReachabilityCreateWithAddress ( nil , UnsafePointer ( $0) )
546
+ } ) else {
547
+ return nil
548
+ }
549
+ self . reachability = reachability
550
+ }
551
+
552
+ var connectionRequired : Bool {
553
+ var flags = SCNetworkReachabilityFlags ( )
554
+ let hasFlags = withUnsafeMutablePointer ( to: & flags) {
555
+ SCNetworkReachabilityGetFlags ( reachability, UnsafeMutablePointer ( $0) )
556
+ }
557
+ guard hasFlags else { return false }
558
+ guard flags. contains ( . reachable) else {
559
+ return true
560
+ }
561
+ return flags. contains ( . connectionRequired) || flags. contains ( . transientConnection)
562
+ }
563
+ }
564
+
565
+ fileprivate func isOffline( _ error: Swift . Error ) -> Bool {
566
+ return Reachability ( ) ? . connectionRequired == true
567
+ }
568
+ #else
569
+ fileprivate func isOffline( _ error: Swift . Error ) -> Bool {
570
+ // TODO: Find a better way to determine reachability on non-Darwin platforms.
571
+ return " \( error) " . contains ( " Could not resolve host " )
572
+ }
573
+ #endif
574
+
0 commit comments