/// <summary> /// Returns the requested <see cref="AppPackage" /> from the local cache opened for read access, /// optionally querying an application store service if the package is not available locally. /// </summary> /// <param name="storeEP"> /// The application store endpoint or <c>null</c> to query any /// application store instance in the cluster. /// </param> /// <param name="appRef">The <see cref="AppRef" /> identifing the desired application package.</param> /// <param name="queryStore"> /// Pass <c>true</c> if an application store is to be queried if the package /// is not cached locally. /// </param> /// <returns></returns> /// <exception cref="ObjectDisposedException">Thrown if the cache is not open.</exception> /// <exception cref="InvalidOperationException">Thrown if local package caching is disabled.</exception> /// <exception cref="AppPackageException">Thrown if the package cannot be found.</exception> public AppPackage GetPackage(MsgEP storeEP, AppRef appRef, bool queryStore) { AppPackageInfo info; string transitPath; if (storeEP == null) { storeEP = settings.ClusterEP; } using (TimedLock.Lock(syncLock)) { VerifyOpen(); VerifyLocal(); info = packageFolder.GetPackageInfo(appRef); if (info != null) { return(AppPackage.Open(info.FullPath)); } } // $hack(jeff.lill): // // The code below isn't strictly threadsafe // but should exhibit problems only when // closing the client under load so I'm // not going to worry about this right now. // Download the package from an application store. transitPath = packageFolder.BeginTransit(appRef); try { DownloadPackage(null, appRef, transitPath); packageFolder.EndTransit(transitPath, true); } catch { packageFolder.EndTransit(transitPath, false); throw; } info = packageFolder.GetPackageInfo(appRef); if (info == null) { throw new AppPackageException("Package [{0}] not found.", appRef); } return(AppPackage.Open(info.FullPath)); }
/// <summary> /// Completes the loading of an application package file. /// </summary> /// <param name="path">The fully qualified path returned by <see cref="BeginTransit" />.</param> /// <param name="commit"> /// Pass <c>true</c> to commit the package to the folder, <c>false</c> to /// delete the transit file without committing it. /// </param> public void EndTransit(string path, bool commit) { if (commit) { AppPackage package; AppPackageInfo info; int size; string destPath; using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) size = (int)fs.Length; using (TimedLock.Lock(syncLock)) { try { fileWatcher.EnableRaisingEvents = false; destPath = packageFolder + Path.GetFileName(path); if (File.Exists(destPath)) { File.Delete(destPath); } File.Move(path, destPath); package = null; try { package = AppPackage.Open(destPath); info = new AppPackageInfo(package.AppRef, Path.GetFileName(destPath), destPath, package.MD5, size, File.GetLastWriteTimeUtc(destPath)); } catch { if (package == null) { // The package must be bad so delete it. Helper.DeleteFile(destPath); } throw; } finally { if (package != null) { package.Close(); } } packages[info.AppRef] = info; } finally { fileWatcher.EnableRaisingEvents = true; } } RaiseChangeEvent(); } else { Helper.DeleteFile(path); } }
/// <summary> /// The actual internal folder scan implementation. /// </summary> private void InternalScan() { if (isDisposed) { return; } try { if (Interlocked.Increment(ref scanning) > 1) { return; // Don't allow multiple parallel scans } Dictionary <AppRef, AppPackageInfo> newPackages = new Dictionary <AppRef, AppPackageInfo>(); string[] files; files = Helper.GetFilesByPattern(packageFolder + "*.zip", SearchOption.TopDirectoryOnly); foreach (string file in files) { bool isRetry = false; retry: try { int size; using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read)) size = (int)fs.Length; using (AppPackage package = AppPackage.Open(file)) newPackages.Add(package.AppRef, new AppPackageInfo(package.AppRef, Path.GetFileName(file), file, package.MD5, size, File.GetLastWriteTimeUtc(file))); } catch (FileNotFoundException e) { SysLog.LogErrorStackDump("{0}", e.Message); } catch (DirectoryNotFoundException e) { SysLog.LogErrorStackDump("{0}", e.Message); } catch (IOException e) { // I've run into trouble where we've gotten notifications from the // file watcher while a new file is still locked for writing. In // this situation, I'm going to wait a bit and then retry the // operation once. if (!isRetry) { Thread.Sleep(RetryTime); isRetry = true; goto retry; } SysLog.LogErrorStackDump("{0}", e.Message); } catch (Exception e) { SysLog.LogErrorStackDump("Error [{0}] loading application package [{1}].", e.Message, file); } } // Determine if there have been any changes to the set of // application packages and then update the cached package set // and then raise the change event if there were any changes. bool changed = false; using (TimedLock.Lock(syncLock)) { foreach (var info in newPackages.Values) { if (!packages.ContainsKey(info.AppRef)) { changed = true; break; } else { var orgInfo = packages[info.AppRef]; if (info.FileName != orgInfo.FileName || !Helper.ArrayEquals(info.MD5, orgInfo.MD5)) { changed = true; break; } } } if (!changed) { foreach (AppPackageInfo info in packages.Values) { if (!newPackages.ContainsKey(info.AppRef)) { changed = true; break; } } } if (changed) { packages = newPackages; } } if (changed) { RaiseChangeEvent(); } } finally { Interlocked.Decrement(ref scanning); } }