@@ -108,6 +108,35 @@ internal ServerPackageRepository(
108108
109109 private string CacheFileName => _settingsProvider . GetStringSetting ( "cacheFileName" , null ) ;
110110
111+ private TimeSpan InitialCacheRebuildAfter
112+ {
113+ get
114+ {
115+ var value = GetPositiveIntSetting ( "initialCacheRebuildAfterSeconds" , 15 ) ;
116+ return TimeSpan . FromSeconds ( value ) ;
117+ }
118+ }
119+
120+ private TimeSpan CacheRebuildFrequency
121+ {
122+ get
123+ {
124+ int value = GetPositiveIntSetting ( "cacheRebuildFrequencyInMinutes" , 60 ) ;
125+ return TimeSpan . FromMinutes ( value ) ;
126+ }
127+ }
128+
129+ private int GetPositiveIntSetting ( string name , int defaultValue )
130+ {
131+ var value = _settingsProvider . GetIntSetting ( name , defaultValue ) ;
132+ if ( value <= 0 )
133+ {
134+ value = defaultValue ;
135+ }
136+
137+ return value ;
138+ }
139+
111140 private ServerPackageCache InitializeServerPackageCache ( )
112141 {
113142 return new ServerPackageCache ( _fileSystem , ResolveCacheFileName ( ) ) ;
@@ -529,18 +558,21 @@ private void SetupBackgroundJobs()
529558 _logger . Log ( LogLevel . Info , "Registering background jobs..." ) ;
530559
531560 // Persist to package store at given interval (when dirty)
561+ _logger . Log ( LogLevel . Info , "Persisting the cache file every 1 minute." ) ;
532562 _persistenceTimer = new Timer (
533563 callback : state => _serverPackageCache . PersistIfDirty ( ) ,
534564 state : null ,
535565 dueTime : TimeSpan . FromMinutes ( 1 ) ,
536566 period : TimeSpan . FromMinutes ( 1 ) ) ;
537567
538- // Rebuild the package store in the background (every hour)
568+ // Rebuild the package store in the background
569+ _logger . Log ( LogLevel . Info , "Rebuilding the cache file for the first time after {0} second(s)." , InitialCacheRebuildAfter . TotalSeconds ) ;
570+ _logger . Log ( LogLevel . Info , "Rebuilding the cache file every {0} hour(s)." , CacheRebuildFrequency . TotalHours ) ;
539571 _rebuildTimer = new Timer (
540572 callback : state => RebuildPackageStoreAsync ( CancellationToken . None ) ,
541573 state : null ,
542- dueTime : TimeSpan . FromSeconds ( 15 ) ,
543- period : TimeSpan . FromHours ( 1 ) ) ;
574+ dueTime : InitialCacheRebuildAfter ,
575+ period : CacheRebuildFrequency ) ;
544576
545577 _logger . Log ( LogLevel . Info , "Finished registering background jobs." ) ;
546578 }
@@ -595,7 +627,6 @@ private void UnregisterFileSystemWatcher()
595627 _watchDirectory = null ;
596628 }
597629
598-
599630 /// <summary>
600631 /// This is an event handler for background work. Therefore, it should never throw exceptions.
601632 /// </summary>
@@ -608,6 +639,12 @@ private async void FileSystemChangedAsync(object sender, FileSystemEventArgs e)
608639 return ;
609640 }
610641
642+ if ( ShouldIgnoreFileSystemEvent ( e ) )
643+ {
644+ _logger . Log ( LogLevel . Verbose , "File system event ignored. File: {0} - Change: {1}" , e . Name , e . ChangeType ) ;
645+ return ;
646+ }
647+
611648 _logger . Log ( LogLevel . Verbose , "File system changed. File: {0} - Change: {1}" , e . Name , e . ChangeType ) ;
612649
613650 var changedDirectory = Path . GetDirectoryName ( e . FullPath ) ;
@@ -642,6 +679,59 @@ private async void FileSystemChangedAsync(object sender, FileSystemEventArgs e)
642679 }
643680 }
644681
682+ private bool ShouldIgnoreFileSystemEvent ( FileSystemEventArgs e )
683+ {
684+ // We can only ignore Created or Changed events. All other types are always processed. Eventually we could
685+ // try to ignore some Deleted events in the case of API package delete, but this is harder.
686+ if ( e . ChangeType != WatcherChangeTypes . Created
687+ && e . ChangeType != WatcherChangeTypes . Changed )
688+ {
689+ _logger . Log ( LogLevel . Verbose , "The file system event change type is not ignorable." ) ;
690+ return false ;
691+ }
692+
693+ /// We can only ignore events related to file paths changed by the
694+ /// <see cref="ExpandedPackageRepository"/>. If the file system event is representing a known file path
695+ /// extracted during package push, we can ignore the event. File system events are supressed during package
696+ /// push but this is still necessary since file system events can come some time after the suppression
697+ /// window has ended.
698+ if ( ! KnownPathUtility . TryParseFileName ( e . Name , out var id , out var version ) )
699+ {
700+ _logger . Log ( LogLevel . Verbose , "The file system event is not related to a known package path." ) ;
701+ return false ;
702+ }
703+
704+ /// The file path could have been generated by <see cref="ExpandedPackageRepository"/>. Now
705+ /// determine if the package is in the cache.
706+ var matchingPackage = _serverPackageCache
707+ . GetAll ( )
708+ . Where ( p => StringComparer . OrdinalIgnoreCase . Equals ( p . Id , id ) )
709+ . Where ( p => version . Equals ( p . Version ) )
710+ . FirstOrDefault ( ) ;
711+
712+ if ( matchingPackage == null )
713+ {
714+ _logger . Log ( LogLevel . Verbose , "The file system event is not related to a known package." ) ;
715+ return false ;
716+ }
717+
718+ var fileInfo = new FileInfo ( e . FullPath ) ;
719+ if ( ! fileInfo . Exists )
720+ {
721+ _logger . Log ( LogLevel . Verbose , "The package file is missing." ) ;
722+ return false ;
723+ }
724+
725+ var minimumCreationTime = DateTimeOffset . UtcNow . AddMinutes ( - 1 ) ;
726+ if ( fileInfo . CreationTimeUtc < minimumCreationTime )
727+ {
728+ _logger . Log ( LogLevel . Verbose , "The package file was not created recently." ) ;
729+ return false ;
730+ }
731+
732+ return true ;
733+ }
734+
645735 private async Task < Lock > LockAsync ( CancellationToken token )
646736 {
647737 var handle = new Lock ( _syncLock ) ;
@@ -710,8 +800,8 @@ public void Dispose()
710800 {
711801 if ( _lockHandle != null && _lockHandle . LockTaken )
712802 {
713- _lockHandle . Dispose ( ) ;
714803 _repository . _isFileSystemWatcherSuppressed = false ;
804+ _lockHandle . Dispose ( ) ;
715805 }
716806 }
717807 }
0 commit comments