/// <summary> /// Creates a new <see cref="VersionFileList"/>. /// </summary> /// <param name="rootDir">The root directory containing the files to include.</param> /// <param name="filters">The filters to include.</param> /// <returns>The <see cref="VersionFileList"/> created from the given parameters.</returns> public static VersionFileList Create(string rootDir, IEnumerable <string> filters) { if (log.IsDebugEnabled) { log.DebugFormat("VersionFileList.Create(rootDir: {0}, fiters: ...)", rootDir); } var addFiles = new List <VersionFileInfo>(); var files = Directory.GetFiles(rootDir, "*", SearchOption.AllDirectories); // Get the length of the root directory so we know how much to chop off so that each file starts relative to // the rootDir, and does not start with a path separator var rootDirLen = rootDir.Length; if (!rootDir.EndsWith(Path.DirectorySeparatorChar.ToString()) && !rootDir.EndsWith(Path.AltDirectorySeparatorChar.ToString())) { rootDirLen++; } foreach (var f in files) { // Get the file information var fi = new FileInfo(f); // Generate the properties we are interested in var relativePath = fi.FullName.Substring(rootDirLen); var hash = Hasher.GetFileHash(fi.FullName); var size = fi.Length; // Add to the list var vfi = new VersionFileInfo(relativePath, hash, size); addFiles.Add(vfi); } var vfl = new VersionFileList(addFiles, filters); return(vfl); }
/// <summary> /// Creates the <see cref="VersionFileList"/> from a string. /// </summary> /// <param name="contents">The version file list contents.</param> /// <returns>The <see cref="VersionFileList"/> loaded from the <paramref name="contents"/>.</returns> /// <exception cref="InvalidDataException">Any of the lines are invalid.</exception> public static VersionFileList CreateFromString(string contents) { if (log.IsDebugEnabled) log.DebugFormat("VersionFileList.CreateFromString(contents: {0})", contents); var lines = contents.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); // Create the lists var files = new List<VersionFileInfo>(); var filters = new List<string>(); // Add to file list instead of the filters by default var addToFiles = true; var strComp = StringComparer.OrdinalIgnoreCase; foreach (var line in lines) { if (string.IsNullOrEmpty(line)) continue; var l = line.Trim(); // Check for a header if (strComp.Equals(IncludeHeader, l)) { addToFiles = true; continue; } else if (strComp.Equals(IgnoreHeader, l)) { addToFiles = false; continue; } // Not a header, so split according to the type if (addToFiles) { var vfi = VersionFileInfo.FromString(l); files.Add(vfi); } else { // Expect a line for a filter if (l.Contains(Delimiter)) { const string errmsg = "Was expecting a line containing a filter on line: {0}"; if (log.IsErrorEnabled) log.ErrorFormat(errmsg, line); throw new InvalidDataException(string.Format(errmsg, line)); } filters.Add(l); } } var vfl = new VersionFileList(files, filters); return vfl; }
/// <summary> /// Creates a new <see cref="VersionFileList"/>. /// </summary> /// <param name="rootDir">The root directory containing the files to include.</param> /// <param name="filters">The filters to include.</param> /// <returns>The <see cref="VersionFileList"/> created from the given parameters.</returns> public static VersionFileList Create(string rootDir, IEnumerable<string> filters) { if (log.IsDebugEnabled) log.DebugFormat("VersionFileList.Create(rootDir: {0}, fiters: ...)", rootDir); var addFiles = new List<VersionFileInfo>(); var files = Directory.GetFiles(rootDir, "*", SearchOption.AllDirectories); // Get the length of the root directory so we know how much to chop off so that each file starts relative to // the rootDir, and does not start with a path separator var rootDirLen = rootDir.Length; if (!rootDir.EndsWith(Path.DirectorySeparatorChar.ToString()) && !rootDir.EndsWith(Path.AltDirectorySeparatorChar.ToString())) rootDirLen++; foreach (var f in files) { // Get the file information var fi = new FileInfo(f); // Generate the properties we are interested in var relativePath = fi.FullName.Substring(rootDirLen); var hash = Hasher.GetFileHash(fi.FullName); var size = fi.Length; // Add to the list var vfi = new VersionFileInfo(relativePath, hash, size); addFiles.Add(vfi); } var vfl = new VersionFileList(addFiles, filters); return vfl; }
/// <summary> /// The callback method for the <see cref="IMasterServerReader.BeginReadVersionFileList"/>. /// </summary> /// <param name="sender">The <see cref="IMasterServerReader"/> this event came from.</param> /// <param name="info">The information from the master server(s).</param> /// <param name="userState">An optional state object passed by the caller to supply information to the callback method /// from the method call.</param> void MasterServerReader_Callback_VersionFileList(IMasterServerReader sender, IMasterServerReadInfo info, object userState) { State = UpdateClientState.ReadingLiveVersionFileListDone; // Check for a valid VersionFileList if (string.IsNullOrEmpty(info.VersionFileListText)) { const string errmsg = "Could not get a valid VersionFileList file from the master servers for version `{0}` - download failed."; if (log.IsErrorEnabled) log.ErrorFormat(errmsg, info.Version); if (MasterServerReaderError != null) MasterServerReaderError(this, string.Format(errmsg, info.Version)); HasErrors = true; return; } try { _versionFileList = VersionFileList.CreateFromString(info.VersionFileListText); } catch (Exception ex) { const string errmsg = "Could not get a valid VersionFileList file from the master servers for version `{0}`. Exception: {1}"; if (log.IsErrorEnabled) log.ErrorFormat(errmsg, info.Version, ex); if (MasterServerReaderError != null) MasterServerReaderError(this, string.Format(errmsg, info.Version, ex)); HasErrors = true; return; } // Find the files to update var toUpdate = FindFilesToUpdate(_versionFileList); // If all file hashes match, then we are good to go if (toUpdate.Count() == 0) { CheckIfDownloadManagerComplete(); return; } // There was one or more files to update, so start the updating... // Create the DownloadManager _dm = new DownloadManager(Settings.TargetPath, Settings.TempPath, info.Version); _dm.DownloadFinished += DownloadManager_DownloadFinished; _dm.FileMoveFailed += DownloadManager_FileMoveFailed; _dm.DownloadFailed += DownloadManager_DownloadFailed; _dm.Finished += DownloadManager_Finished; State = UpdateClientState.UpdatingFiles; // Add the sources to the DownloadManager var sources = info.DownloadSources.Select(x => x.Instantiate()); _dm.AddSources(sources); // Enqueue the files that need to be downloaded _dm.Enqueue(toUpdate); }
/// <summary> /// Checks the files provided in the given <see cref="VersionFileList"/> and compares them to the local files /// to see what files need to be updated. /// </summary> /// <param name="vfl">The <see cref="VersionFileList"/> containing the files to be updated.</param> /// <returns>The relative paths to the files that need to be updated.</returns> IEnumerable<string> FindFilesToUpdate(VersionFileList vfl) { var ret = new List<string>(); // Loop through each file foreach (var updateFileInfo in vfl.Files) { // Get the local file path var localFilePath = PathHelper.CombineDifferentPaths(Settings.TargetPath, updateFileInfo.FilePath); // If the file does not exist, add it if (!File.Exists(localFilePath)) { ret.Add(updateFileInfo.FilePath); continue; } // Get the info for the local file try { var localFileInfo = new FileInfo(localFilePath); // If the size of the local file doesn't equal the size of the updated file, avoid // checking the hash and just update it if (localFileInfo.Length != updateFileInfo.Size) { if (log.IsDebugEnabled) { log.DebugFormat( "Update check on file `{0}`: File needs update - size mismatch (current: {1}, expected: {2}).", updateFileInfo.FilePath, localFileInfo.Length, updateFileInfo.Size); } ret.Add(updateFileInfo.FilePath); continue; } // File exists and is of the correct size, so compare the hash of the local file to the expected hash var localFileHash = Hasher.GetFileHash(localFilePath); if (!StringComparer.Ordinal.Equals(localFileHash, updateFileInfo.Hash)) { if (log.IsDebugEnabled) { log.DebugFormat( "Update check on file `{0}`: File needs update - hash mismatch (current: {1}, expected: {2}).", updateFileInfo.FilePath, localFileHash, updateFileInfo.Hash); } ret.Add(updateFileInfo.FilePath); continue; } } catch (IOException ex) { const string errmsg = "Failed to analyze file `{0}` to see if it needs to be updated. Will assume update is required. Exception: {1}"; if (log.IsErrorEnabled) log.ErrorFormat(errmsg, updateFileInfo.FilePath, ex); Debug.Fail(string.Format(errmsg, updateFileInfo.FilePath, ex)); // On an IOException, assume the file needs to be updated ret.Add(updateFileInfo.FilePath); continue; } if (log.IsDebugEnabled) log.DebugFormat("Update check on file `{0}`: file is up-to-date.", updateFileInfo.FilePath); } if (log.IsInfoEnabled) log.InfoFormat("Found `{0}` files needing to be updated.", ret.Count); return ret; }
/// <summary> /// Creates the <see cref="VersionFileList"/> from a string. /// </summary> /// <param name="contents">The version file list contents.</param> /// <returns>The <see cref="VersionFileList"/> loaded from the <paramref name="contents"/>.</returns> /// <exception cref="InvalidDataException">Any of the lines are invalid.</exception> public static VersionFileList CreateFromString(string contents) { if (log.IsDebugEnabled) { log.DebugFormat("VersionFileList.CreateFromString(contents: {0})", contents); } var lines = contents.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); // Create the lists var files = new List <VersionFileInfo>(); var filters = new List <string>(); // Add to file list instead of the filters by default var addToFiles = true; var strComp = StringComparer.OrdinalIgnoreCase; foreach (var line in lines) { if (string.IsNullOrEmpty(line)) { continue; } var l = line.Trim(); // Check for a header if (strComp.Equals(IncludeHeader, l)) { addToFiles = true; continue; } else if (strComp.Equals(IgnoreHeader, l)) { addToFiles = false; continue; } // Not a header, so split according to the type if (addToFiles) { var vfi = VersionFileInfo.FromString(l); files.Add(vfi); } else { // Expect a line for a filter if (l.Contains(Delimiter)) { const string errmsg = "Was expecting a line containing a filter on line: {0}"; if (log.IsErrorEnabled) { log.ErrorFormat(errmsg, line); } throw new InvalidDataException(string.Format(errmsg, line)); } filters.Add(l); } } var vfl = new VersionFileList(files, filters); return(vfl); }
/// <summary> /// The callback method for the <see cref="IMasterServerReader.BeginReadVersionFileList"/>. /// </summary> /// <param name="sender">The <see cref="IMasterServerReader"/> this event came from.</param> /// <param name="info">The information from the master server(s).</param> /// <param name="userState">An optional state object passed by the caller to supply information to the callback method /// from the method call.</param> void MasterServerReader_Callback_VersionFileList(IMasterServerReader sender, IMasterServerReadInfo info, object userState) { State = UpdateClientState.ReadingLiveVersionFileListDone; // Check for a valid VersionFileList if (string.IsNullOrEmpty(info.VersionFileListText)) { const string errmsg = "Could not get a valid VersionFileList file from the master servers for version `{0}` - download failed."; if (log.IsErrorEnabled) { log.ErrorFormat(errmsg, info.Version); } if (MasterServerReaderError != null) { MasterServerReaderError(this, string.Format(errmsg, info.Version)); } HasErrors = true; return; } try { _versionFileList = VersionFileList.CreateFromString(info.VersionFileListText); } catch (Exception ex) { const string errmsg = "Could not get a valid VersionFileList file from the master servers for version `{0}`. Exception: {1}"; if (log.IsErrorEnabled) { log.ErrorFormat(errmsg, info.Version, ex); } if (MasterServerReaderError != null) { MasterServerReaderError(this, string.Format(errmsg, info.Version, ex)); } HasErrors = true; return; } // Find the files to update var toUpdate = FindFilesToUpdate(_versionFileList); // If all file hashes match, then we are good to go if (toUpdate.Count() == 0) { CheckIfDownloadManagerComplete(); return; } // There was one or more files to update, so start the updating... // Create the DownloadManager _dm = new DownloadManager(Settings.TargetPath, Settings.TempPath, info.Version); _dm.DownloadFinished += DownloadManager_DownloadFinished; _dm.FileMoveFailed += DownloadManager_FileMoveFailed; _dm.DownloadFailed += DownloadManager_DownloadFailed; _dm.Finished += DownloadManager_Finished; State = UpdateClientState.UpdatingFiles; // Add the sources to the DownloadManager var sources = info.DownloadSources.Select(x => x.Instantiate()); _dm.AddSources(sources); // Enqueue the files that need to be downloaded _dm.Enqueue(toUpdate); }
/// <summary> /// Checks the files provided in the given <see cref="VersionFileList"/> and compares them to the local files /// to see what files need to be updated. /// </summary> /// <param name="vfl">The <see cref="VersionFileList"/> containing the files to be updated.</param> /// <returns>The relative paths to the files that need to be updated.</returns> IEnumerable <string> FindFilesToUpdate(VersionFileList vfl) { var ret = new List <string>(); // Loop through each file foreach (var updateFileInfo in vfl.Files) { // Get the local file path var localFilePath = PathHelper.CombineDifferentPaths(Settings.TargetPath, updateFileInfo.FilePath); // If the file does not exist, add it if (!File.Exists(localFilePath)) { ret.Add(updateFileInfo.FilePath); continue; } // Get the info for the local file try { var localFileInfo = new FileInfo(localFilePath); // If the size of the local file doesn't equal the size of the updated file, avoid // checking the hash and just update it if (localFileInfo.Length != updateFileInfo.Size) { if (log.IsDebugEnabled) { log.DebugFormat( "Update check on file `{0}`: File needs update - size mismatch (current: {1}, expected: {2}).", updateFileInfo.FilePath, localFileInfo.Length, updateFileInfo.Size); } ret.Add(updateFileInfo.FilePath); continue; } // File exists and is of the correct size, so compare the hash of the local file to the expected hash var localFileHash = Hasher.GetFileHash(localFilePath); if (!StringComparer.Ordinal.Equals(localFileHash, updateFileInfo.Hash)) { if (log.IsDebugEnabled) { log.DebugFormat( "Update check on file `{0}`: File needs update - hash mismatch (current: {1}, expected: {2}).", updateFileInfo.FilePath, localFileHash, updateFileInfo.Hash); } ret.Add(updateFileInfo.FilePath); continue; } } catch (IOException ex) { const string errmsg = "Failed to analyze file `{0}` to see if it needs to be updated. Will assume update is required. Exception: {1}"; if (log.IsErrorEnabled) { log.ErrorFormat(errmsg, updateFileInfo.FilePath, ex); } Debug.Fail(string.Format(errmsg, updateFileInfo.FilePath, ex)); // On an IOException, assume the file needs to be updated ret.Add(updateFileInfo.FilePath); continue; } if (log.IsDebugEnabled) { log.DebugFormat("Update check on file `{0}`: file is up-to-date.", updateFileInfo.FilePath); } } if (log.IsInfoEnabled) { log.InfoFormat("Found `{0}` files needing to be updated.", ret.Count); } return(ret); }
/// <summary> /// Adds the text read for the <see cref="VersionFileList"/>. This method will use a "best guess" approach /// to determine what text to use when multiple different texts are added. /// </summary> /// <param name="text">The text for the <see cref="VersionFileList"/>.</param> public void AddVersionFileListText(string text) { if (log.IsDebugEnabled) { log.DebugFormat("Adding VersionFileList text `{0}` to MasterServerReadInfo `{1}`.", text, this); } lock (_versionFileListTextSync) { // If the text is exactly the same as the current _versionFileListText, then just ignore it since there is obviously // nothing to be done if (StringComparer.Ordinal.Equals(text, _versionFileListText)) { return; } // Try to parse the text as a VersionFileList to see if it is valid bool textIsValid; try { VersionFileList.CreateFromString(text); textIsValid = true; } catch (InvalidDataException) { // Ignore InvalidDataException since its expected when the version is invalid textIsValid = false; } catch (Exception ex) { // For any other exception, it still must be invalid, but check it out when debugging Debug.Fail(ex.ToString()); textIsValid = false; } // If no value set, just use the text no matter what if (string.IsNullOrEmpty(_versionFileListText)) { _versionFileListText = text; _currVersionFileListTextLegal = textIsValid; return; } // The _versionFileListText already exists. If the current text is invalid but the new text is valid, then // just use the new text. if (!_currVersionFileListTextLegal && textIsValid) { _versionFileListText = text; _currVersionFileListTextLegal = textIsValid; return; } // Likewise, if the current text is valid but the new text is invalid, do not use the new text if (_currVersionFileListTextLegal && !textIsValid) { return; } // From here, they are either both valid or both invalid. To keep things simple, just assume that the larger // of the two strings are "better". So change to the new text if it is longer. if (text.Length > _versionFileListText.Length) { _versionFileListText = text; _currVersionFileListTextLegal = textIsValid; return; } } }