private void olvJobs_DoubleClick(object sender, EventArgs e) { ApplicationJob job = olvJobs.SelectedObject as ApplicationJob; // Check for custom hotkeys first foreach (Hotkey hotkey in this.hotkeys) { if (hotkey.IsDoubleClickMatch(ModifierKeys)) { ExecuteHotkey(hotkey, job); return; } } if (ModifierKeys == Keys.Control) { OpenDownloadFolder(job); } else if (ModifierKeys == Keys.Alt) { cmnuOpenFile.PerformClick(); } else { bool openWebsite = (bool)Settings.GetValue("OpenWebsiteOnDoubleClick", false); if (openWebsite && !string.IsNullOrEmpty(job.WebsiteUrl)) { OpenWebsite(job); } else { EditJob(job); } } }
protected override void OnResize(EventArgs e) { base.OnResize(e); if (this.WindowState == FormWindowState.Minimized) { if (Convert.ToBoolean(Settings.GetValue("MinimizeToTray", false))) { ntiTrayIcon.Visible = true; this.Hide(); } } else { m_PreviousState = WindowState; } }
protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); Settings.SetValue("Ketarin", "ShowGroups", olvJobs.ShowGroups); Settings.SetValue("Ketarin", "ShowStatusBar", statusBar.Visible); Settings.SetValue("Ketarin", "ShowLog", mnuLog.Checked); Settings.SetValue("Ketarin", "AutoScroll", mnuAutoScroll.Checked); if (m_Updater.IsBusy) { e.Cancel = true; } else { LogDialog.Instance.Close(); } }
/// <summary> /// Checks for which of the given applications updates /// are available asynchronously. /// </summary> public void BeginCheckForOnlineUpdates(ApplicationJob[] jobs) { DateTime lastUpdate = (DateTime)Settings.GetValue("LastUpdateCheck", DateTime.MinValue); if (lastUpdate.Date == DateTime.Now.Date) { // Only check once a day return; } Settings.SetValue("LastUpdateCheck", DateTime.Now); Thread thread = new Thread(this.CheckForOnlineUpdates) { IsBackground = true }; thread.Start(jobs); }
protected override void OnLoad(EventArgs e) { if (DesignMode) { return; } RebuildCustomColumns(); base.OnLoad(e); mnuShowGroups.Checked = Conversion.ToBoolean(Settings.GetValue("Ketarin", "ShowGroups", true)); mnuAutoScroll.Checked = Conversion.ToBoolean(Settings.GetValue("Ketarin", "AutoScroll", true)); olvJobs.ShowGroups = mnuShowGroups.Checked; if (Conversion.ToBoolean(Settings.GetValue("Ketarin", "ShowStatusBar", false))) { mnuShowStatusBar.PerformClick(); } UpdateList(); UpdateNumByStatus(); if (Convert.ToBoolean(Settings.GetValue("Ketarin", "ShowLog", false))) { mnuLog.PerformClick(); } if ((bool)Settings.GetValue("UpdateAtStartup", false)) { RunJobs(false, false, false); } // Check applications for updates if ((bool)Settings.GetValue("UpdateOnlineDatabase", true)) { m_Updater.BeginCheckForOnlineUpdates(m_Jobs); } this.hotkeys = Hotkey.GetHotkeys(); }
/// <summary> /// Handles download failure (set failed state, add to errors) and executes the "update failed" /// command for additional control. /// </summary> private void HandleUpdateFailed(ApplicationJob job, ApplicationJobError error) { // Execute: Default update failed command string updateFailedCommand = Settings.GetValue("UpdateFailedCommand", "") as string; ScriptType defaultPreCommandType = Command.ConvertToScriptType(Settings.GetValue("UpdateFailedCommandType", ScriptType.Batch.ToString()) as string); m_Status[job] = Status.Failure; if (!string.IsNullOrEmpty(updateFailedCommand)) { int exitCode = new Command(updateFailedCommand, defaultPreCommandType).Execute(job, null, error); // Do not show failure in error window. if (exitCode == 1) { LogDialog.Log(job, "Update failed command returned '1', ignoring error"); return; } } m_Errors.Add(error); }
/// <summary> /// Starts one or more threads which update the given /// applications asynchronously. /// </summary> /// <param name="onlyCheck">Specifies whether or not to download the updates</param> public void BeginUpdate(ApplicationJob[] jobs, bool onlyCheck, bool installUpdated) { IsBusy = true; m_Jobs = jobs; m_ThreadLimit = Convert.ToInt32(Settings.GetValue("ThreadCount", 2)); m_OnlyCheck = onlyCheck; m_InstallUpdated = installUpdated; m_Requests.Clear(); // Initialise progress and status m_Progress = new Dictionary <ApplicationJob, short>(); foreach (ApplicationJob job in m_Jobs) { m_Progress[job] = (short)((ForceDownload || job.Enabled) ? 0 : -1); m_Status[job] = Status.Idle; m_Size[job] = -2; } m_Threads.Clear(); Thread thread = new Thread(UpdateApplications); thread.Start(); }
/// <summary> /// Executes the actual download from an URL. Does not handle exceptions, /// but takes care of proper cleanup. /// </summary> /// <exception cref="NonBinaryFileException">This exception is thrown, if the resulting file is not of a binary type</exception> /// <exception cref="TargetPathInvalidException">This exception is thrown, if the resulting target path of an application is not valid</exception> /// <param name="job">The job to process</param> /// <param name="urlToRequest">URL from which should be downloaded</param> /// <returns>true, if a new update has been found and downloaded, false otherwise</returns> protected Status DoDownload(ApplicationJob job, Uri urlToRequest) { // Determine number of segments to create int segmentCount = Convert.ToInt32(Settings.GetValue("SegmentCount", 1)); job.Variables.ResetDownloadCount(); WebRequest req = GeGeekProtocolProvider.CreateRequest(urlToRequest, job, this.m_Cookies); AddRequestToCancel(req); using (WebResponse response = WebClient.GetResponse(req)) { LogDialog.Log(job, "Server source file: " + req.RequestUri.AbsolutePath); // Occasionally, websites are not available and an error page is encountered // For the case that the content type is just plain wrong, ignore it if the size is higher than 500KB HttpWebResponse httpResponse = response as HttpWebResponse; if (httpResponse != null && response.ContentLength < 500000) { if (response.ContentType.StartsWith("text/xml") || response.ContentType.StartsWith("application/xml")) { // If an XML file is served, maybe we have a PAD file ApplicationJob padJob = ApplicationJob.ImportFromPad(httpResponse); if (padJob != null) { job.CachedPadFileVersion = padJob.CachedPadFileVersion; return(this.DoDownload(job, new Uri(padJob.FixedDownloadUrl))); } } if (response.ContentType.StartsWith("text/html")) { bool avoidNonBinary = (bool)Settings.GetValue("AvoidDownloadingNonBinaryFiles", true); if (httpResponse.StatusCode != HttpStatusCode.OK || avoidNonBinary) { throw NonBinaryFileException.Create(response.ContentType, httpResponse.StatusCode); } } } long fileSize = GetContentLength(response); if (fileSize == 0) { throw new IOException("Source file on server is empty (ContentLength = 0)."); } string targetFileName = job.GetTargetFile(response, urlToRequest.AbsoluteUri); LogDialog.Log(job, "Determined target file name: " + targetFileName); // Only download, if the file size or date has changed if (!ForceDownload && !job.RequiresDownload(response, targetFileName)) { // If file already exists (created by user), // the download is not necessary. We still need to // set the file name. // If the file exists, but not at the target location // (after renaming), do not reset the previous location. if (File.Exists(targetFileName)) { job.PreviousLocation = targetFileName; } job.Save(); return(Status.NoUpdate); } // Skip downloading! // Installing also requires a forced download if (!ForceDownload && !m_InstallUpdated && (m_OnlyCheck || (job.CheckForUpdatesOnly && !IgnoreCheckForUpdatesOnly))) { LogDialog.Log(job, "Skipped downloading updates"); return(Status.UpdateAvailable); } // Execute: Default pre-update command string defaultPreCommand = Settings.GetValue("PreUpdateCommand", "") as string; // For starting external download managers: {preupdate-url} defaultPreCommand = UrlVariable.Replace(defaultPreCommand, "preupdate-url", urlToRequest.ToString(), job); ScriptType defaultPreCommandType = Command.ConvertToScriptType(Settings.GetValue("PreUpdateCommandType", ScriptType.Batch.ToString()) as string); int exitCode = new Command(defaultPreCommand, defaultPreCommandType).Execute(job, targetFileName); if (exitCode == 1) { LogDialog.Log(job, "Default pre-update command returned '1', download aborted"); throw new CommandErrorException(); } else if (exitCode == 2) { LogDialog.Log(job, "Default pre-update command returned '2', download skipped"); return(Status.UpdateAvailable); } // Execute: Application pre-update command exitCode = new Command(UrlVariable.Replace(job.ExecutePreCommand, "preupdate-url", urlToRequest.ToString(), job), job.ExecutePreCommandType).Execute(job, targetFileName); if (exitCode == 1) { LogDialog.Log(job, "Pre-update command returned '1', download aborted"); throw new CommandErrorException(); } else if (exitCode == 2) { LogDialog.Log(job, "Pre-update command returned '2', download skipped"); return(Status.UpdateAvailable); } else if (exitCode == 3) { LogDialog.Log(job, "Pre-update command returned '3', external download"); job.LastUpdated = DateTime.Now; job.Save(); return(Status.UpdateSuccessful); } // Read all file contents to a temporary location string tmpLocation = Path.GetTempFileName(); DateTime lastWriteTime = ApplicationJob.GetLastModified(response); // Only use segmented downloader with more than one segment. if (segmentCount > 1) { // Response can be closed now, new one will be created. response.Dispose(); m_Size[job] = fileSize; Downloader d = new Downloader(new ResourceLocation { Url = urlToRequest.AbsoluteUri, ProtocolProvider = new GeGeekProtocolProvider(job, m_Cookies) }, null, tmpLocation, segmentCount); d.Start(); while (d.State < DownloaderState.Ended) { if (m_CancelUpdates) { d.Pause(); break; } this.OnProgressChanged(d.Segments.Sum(x => x.Transfered), fileSize, job); Thread.Sleep(250); } if (d.State == DownloaderState.EndedWithError) { throw d.LastError; } } else { // Read contents from the web and put into file using (Stream sourceFile = response.GetResponseStream()) { using (FileStream targetFile = File.Create(tmpLocation)) { long byteCount = 0; int readBytes; m_Size[job] = fileSize; // Only create buffer once and re-use. const int bufferSize = 1024 * 1024; byte[] buffer = new byte[bufferSize]; do { if (m_CancelUpdates) { break; } // Some adjustment for SCP download: Read only up to the max known bytes int maxRead = (fileSize > 0) ? (int)Math.Min(fileSize - byteCount, bufferSize) : bufferSize; if (maxRead == 0) { break; } readBytes = sourceFile.Read(buffer, 0, maxRead); if (readBytes > 0) { targetFile.Write(buffer, 0, readBytes); } byteCount += readBytes; this.OnProgressChanged(byteCount, fileSize, job); } while (readBytes > 0); } } } if (m_CancelUpdates) { m_Progress[job] = 0; OnStatusChanged(job); return(Status.Failure); } // If each version has a different file name (version number), // we might only want to keep one of them. Also, we might // want to free some space on the target location. if (job.DeletePreviousFile) { PathEx.TryDeleteFiles(job.PreviousLocation); } try { File.SetLastWriteTime(tmpLocation, lastWriteTime); } catch (ArgumentException) { // Invalid file date. Ignore and just use DateTime.Now } // File downloaded. Now let's check if the hash value is valid or abort otherwise! if (!string.IsNullOrEmpty(job.HashVariable) && job.HashType != HashType.None) { string varName = job.HashVariable.Trim('{', '}'); string expectedHash = job.Variables.ReplaceAllInString("{" + varName + "}").Trim(); // Compare online hash with actual current hash. if (!string.IsNullOrEmpty(expectedHash)) { string currentHash = job.GetFileHash(tmpLocation); if (string.Compare(expectedHash, currentHash, StringComparison.OrdinalIgnoreCase) != 0) { LogDialog.Log(job, string.Format("File downloaded, but hash of downloaded file {0} does not match the expected hash {1}.", currentHash, expectedHash)); File.Delete(tmpLocation); throw new IOException("Hash verification failed."); } } } try { FileInfo downloadedFileInfo = new FileInfo(tmpLocation); job.LastFileSize = downloadedFileInfo.Length; job.LastFileDate = downloadedFileInfo.LastWriteTime; } catch (Exception ex) { LogDialog.Log(job, ex); } try { // Before copying, we might have to create the directory Directory.CreateDirectory(Path.GetDirectoryName(targetFileName)); // Copying might fail if variables have been replaced with bad values. // However, we cannot rely on functions to clean up the path, since they // might actually parse the path incorrectly and return an even worse path. File.Copy(tmpLocation, targetFileName, true); } catch (ArgumentException) { throw new TargetPathInvalidException(targetFileName); } catch (NotSupportedException) { throw new TargetPathInvalidException(targetFileName); } File.Delete(tmpLocation); // At this point, the update is complete job.LastUpdated = DateTime.Now; job.PreviousLocation = targetFileName; } job.Save(); job.ExecutePostUpdateCommands(); return(Status.UpdateSuccessful); }
/// <summary> /// Performs the update process of a single application. /// Catches most exceptions and stores them for later use. /// </summary> private void StartNewThread(object paramJob) { ApplicationJob job = paramJob as ApplicationJob; m_Status[job] = Status.Downloading; OnStatusChanged(job); string requestedUrl = string.Empty; int numTries = 0; int maxTries = Convert.ToInt32(Settings.GetValue("RetryCount", 1)); try { while (numTries < maxTries) { try { numTries++; m_Status[job] = DoDownload(job, out requestedUrl); // If there is a custom column variable, and it has not been been downloaded yet, // make sure that we fetch it now "unnecessarily" so that the column contains a current value. Dictionary <string, string> customColumns = SettingsDialog.CustomColumns; foreach (KeyValuePair <string, string> column in customColumns) { if (!string.IsNullOrEmpty(column.Value) && !job.Variables.HasVariableBeenDownloaded(column.Value)) { job.Variables.ReplaceAllInString("{" + column.Value.TrimStart('{').TrimEnd('}') + "}"); } } if (customColumns.Count > 0) { job.Save(); // cached variable content } // Install if updated if (m_InstallUpdated && m_Status[job] == Status.UpdateSuccessful) { job.Install(null); } // If no exception happened, we immediately leave the loop break; } catch (SQLiteException ex) { // If "locked" exception (slow USB device eg.) continue trying if (ex.ErrorCode == (int)SQLiteErrorCode.Locked) { numTries--; LogDialog.Log(job, ex); } else { throw; } } catch (Exception ex) { WebException webException = ex as WebException; if (webException != null && webException.Status == WebExceptionStatus.RequestCanceled) { // User cancelled the process -> Do nothing m_Status[job] = Status.Failure; break; } // Only throw an exception if we have run out of tries if (numTries == maxTries) { throw; } else { LogDialog.Log(job, ex); } } } } catch (WebException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, (ex.Response != null) ? ex.Response.ResponseUri.ToString() : requestedUrl)); } catch (FileNotFoundException ex) { // Executing command failed LogDialog.Log(job, ex); m_Errors.Add(new ApplicationJobError(job, ex)); } catch (Win32Exception ex) { // Executing command failed LogDialog.Log(job, ex); m_Errors.Add(new ApplicationJobError(job, ex)); } catch (IOException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex)); } catch (UnauthorizedAccessException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex)); } catch (UriFormatException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (NotSupportedException ex) { // Invalid URI prefix LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (NonBinaryFileException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (TargetPathInvalidException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (CommandErrorException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } catch (ApplicationException ex) { // Error executing custom C# script LogDialog.Log(job, ex); m_Errors.Add(new ApplicationJobError(job, ex)); } catch (SQLiteException ex) { LogDialog.Log(job, ex); this.HandleUpdateFailed(job, new ApplicationJobError(job, ex, requestedUrl)); } m_Progress[job] = 100; OnStatusChanged(job); }
/// <summary> /// Performs the actual update check for the current applications. /// Starts multiple threads if necessary. /// </summary> private void UpdateApplications() { m_CancelUpdates = false; m_Errors = new List <ApplicationJobError>(); LogDialog.Log(string.Format("Update started with {0} application(s)", m_Jobs.Length)); try { ApplicationJob previousJob = null; foreach (ApplicationJob job in m_Jobs) { // Skip if disabled if (!job.Enabled && m_Jobs.Length > 1) { continue; } // Wait until we can start a new thread: // - Thread limit is not reached // - The next application is not to be downloaded exclusively // - The application previously started is not to be downloaded exclusively // - Setup is taking place while (m_Threads.Count >= m_ThreadLimit || (m_Threads.Count > 0 && (m_InstallUpdated || job.ExclusiveDownload || (previousJob != null && previousJob.ExclusiveDownload)))) { Thread.Sleep(200); foreach (Thread activeThread in m_Threads) { if (!activeThread.IsAlive) { m_Threads.Remove(activeThread); break; } } } // Stop if cancelled if (m_CancelUpdates) { break; } Thread newThread = new Thread(this.StartNewThread); previousJob = job; newThread.Start(job); m_Threads.Add(newThread); } // Now, wait until all threads have finished while (m_Threads.Count > 0) { Thread.Sleep(200); foreach (Thread activeThread in m_Threads) { if (!activeThread.IsAlive) { m_Threads.Remove(activeThread); break; } } } try { string postUpdateCommand = Settings.GetValue("PostUpdateCommand", "") as string; ScriptType postUpdateCommandType = Command.ConvertToScriptType(Settings.GetValue("PostUpdateCommandType", ScriptType.Batch.ToString()) as string); new Command(postUpdateCommand, postUpdateCommandType).Execute(null); } catch (ApplicationException ex) { LogDialog.Log("Post update command failed.", ex); } LogDialog.Log("Update finished"); } finally { IsBusy = false; m_Progress.Clear(); m_Size.Clear(); OnUpdateCompleted(); } }