source: branches/eraser6/CodeReview/Eraser.Manager/DirectExecutor.cs @ 1777

Revision 1777, 28.3 KB checked in by lowjoel, 4 years ago (diff)

Redesigned the Logger paradigm -- instead of a dealing with a logger object to do any form of logging, we have a static Logger class which code can log to, and define log targets to receive those logs. Also included is a LogSession? class which sets the log target for the current thread or for a few threads. Addresses #320: Logging improvements

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net> @17/10/2008
6 * Modified By:
7 *
8 * This file is part of Eraser.
9 *
10 * Eraser is free software: you can redistribute it and/or modify it under the
11 * terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 3 of the License, or (at your option) any later
13 * version.
14 *
15 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
16 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 *
19 * A copy of the GNU General Public License can be found at
20 * <http://www.gnu.org/licenses/>.
21 */
22
23using System;
24using System.Collections.Generic;
25using System.Collections.Specialized;
26using System.Text;
27using System.Threading;
28using System.IO;
29using System.Runtime.Serialization;
30using System.Runtime.Serialization.Formatters.Binary;
31
32using Eraser.Util;
33using Eraser.Util.ExtensionMethods;
34
35namespace Eraser.Manager
36{
37    /// <summary>
38    /// The DirectExecutor class is used by the Eraser GUI directly when the program
39    /// is run without the help of a Service.
40    /// </summary>
41    public class DirectExecutor : Executor
42    {
43        public DirectExecutor()
44        {
45            TaskAdded += OnTaskAdded;
46            TaskDeleted += OnTaskDeleted;
47            tasks = new DirectExecutorTasksCollection(this);
48            thread = new Thread(Main);
49        }
50
51        protected override void Dispose(bool disposing)
52        {
53            if (thread == null || schedulerInterrupt == null)
54                return;
55
56            if (disposing)
57            {
58                thread.Abort();
59                schedulerInterrupt.Set();
60
61                //Wait for the executor thread to exit -- we call some event functions
62                //and these events may need invocation on the main thread. So,
63                //pump messages from the main thread until the thread exits.
64                if (System.Windows.Forms.Application.MessageLoop)
65                {
66                    if (!thread.Join(new TimeSpan(0, 0, 0, 0, 100)))
67                        System.Windows.Forms.Application.DoEvents();
68                }
69
70                //If we are disposing on a secondary thread, or a thread without
71                //a message loop, just wait for the thread to exit indefinitely
72                else
73                    thread.Join();
74
75                schedulerInterrupt.Close();
76            }
77
78            thread = null;
79            schedulerInterrupt = null;
80            base.Dispose(disposing);
81        }
82
83        public override void Run()
84        {
85            thread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
86            thread.Start();
87        }
88
89        public override void QueueTask(Task task)
90        {
91            lock (tasksLock)
92            {
93                //Queue the task to be run immediately.
94                DateTime executionTime = DateTime.Now;
95                if (!scheduledTasks.ContainsKey(executionTime))
96                    scheduledTasks.Add(executionTime, new List<Task>());
97                scheduledTasks[executionTime].Add(task);
98                schedulerInterrupt.Set();
99            }
100        }
101
102        public override void ScheduleTask(Task task)
103        {
104            RecurringSchedule schedule = task.Schedule as RecurringSchedule;
105            if (schedule == null)
106                return;
107
108            DateTime executionTime = (schedule.MissedPreviousSchedule &&
109                ManagerLibrary.Settings.ExecuteMissedTasksImmediately) ?
110                    DateTime.Now : schedule.NextRun;
111
112            lock (tasksLock)
113            {
114                if (!scheduledTasks.ContainsKey(executionTime))
115                    scheduledTasks.Add(executionTime, new List<Task>());
116                scheduledTasks[executionTime].Add(task);
117            }
118        }
119
120        public override void QueueRestartTasks()
121        {
122            lock (tasksLock)
123            {
124                foreach (Task task in Tasks)
125                    if (task.Schedule == Schedule.RunOnRestart)
126                        QueueTask(task);
127            }
128        }
129
130        public override void UnqueueTask(Task task)
131        {
132            lock (tasksLock)
133                for (int i = 0; i != scheduledTasks.Count; ++i)
134                    for (int j = 0; j < scheduledTasks.Values[i].Count; )
135                    {
136                        Task currentTask = scheduledTasks.Values[i][j];
137                        if (currentTask == task &&
138                            (!(currentTask.Schedule is RecurringSchedule) ||
139                                ((RecurringSchedule)currentTask.Schedule).NextRun != scheduledTasks.Keys[i]))
140                        {
141                            scheduledTasks.Values[i].RemoveAt(j);
142                        }
143                        else
144                        {
145                            ++j;
146                        }
147                    }
148        }
149
150        internal override bool IsTaskQueued(Task task)
151        {
152            lock (tasksLock)
153                foreach (KeyValuePair<DateTime, List<Task>> tasks in scheduledTasks)
154                    foreach (Task i in tasks.Value)
155                        if (task == i)
156                            if (task.Schedule is RecurringSchedule)
157                            {
158                                if (((RecurringSchedule)task.Schedule).NextRun != tasks.Key)
159                                    return true;
160                            }
161                            else
162                                return true;
163
164            return false;
165        }
166
167        private void OnTaskAdded(object sender, TaskEventArgs e)
168        {
169            e.Task.TaskEdited += OnTaskEdited;
170        }
171
172        private void OnTaskEdited(object sender, EventArgs e)
173        {
174            //Find all schedule entries containing the task - since the user cannot make
175            //edits to the task when it is queued (only if it is scheduled) remove
176            //all task references and add them back
177            Task task = (Task)sender;
178            lock (tasksLock)
179                for (int i = 0; i != scheduledTasks.Count; ++i)
180                    for (int j = 0; j < scheduledTasks.Values[i].Count; )
181                    {
182                        Task currentTask = scheduledTasks.Values[i][j];
183                        if (currentTask == task)
184                            scheduledTasks.Values[i].RemoveAt(j);
185                        else
186                            j++;
187                    }
188
189            //Then reschedule the task
190            if (task.Schedule is RecurringSchedule)
191                ScheduleTask(task);
192        }
193
194        private void OnTaskDeleted(object sender, TaskEventArgs e)
195        {
196            e.Task.TaskEdited -= OnTaskEdited;
197        }
198
199        public override ExecutorTasksCollection Tasks
200        {
201            get
202            {
203                return tasks;
204            }
205        }
206
207        /// <summary>
208        /// The thread entry point for this object. This object operates on a queue
209        /// and hence the thread will sequentially execute tasks.
210        /// </summary>
211        private void Main()
212        {
213            //The waiting thread will utilize a polling loop to check for new
214            //scheduled tasks. This will be checked every 30 seconds. However,
215            //when the thread is waiting for a new task, it can be interrupted.
216            while (thread.ThreadState != ThreadState.AbortRequested)
217            {
218                //Check for a new task
219                Task task = null;
220                lock (tasksLock)
221                {
222                    while (scheduledTasks.Count != 0)
223                        if (scheduledTasks.Values[0].Count == 0)
224                        {
225                            //Clean all all time slots at the start of the queue which are
226                            //empty
227                            scheduledTasks.RemoveAt(0);
228                        }
229                        else
230                        {
231                            if (scheduledTasks.Keys[0] <= DateTime.Now)
232                            {
233                                List<Task> tasks = scheduledTasks.Values[0];
234                                task = tasks[0];
235                                tasks.RemoveAt(0);
236                            }
237
238                            //Do schedule queue maintenance: clean up all empty timeslots
239                            if (task == null)
240                            {
241                                for (int i = 0; i < scheduledTasks.Count; )
242                                    if (scheduledTasks.Values[i].Count == 0)
243                                        scheduledTasks.RemoveAt(i);
244                                    else
245                                        ++i;
246                            }
247
248                            break;
249                        }
250                }
251
252                if (task != null)
253                {
254                    //Set the currently executing task.
255                    currentTask = task;
256
257                    //Prevent the system from sleeping.
258                    Power.ExecutionState = ExecutionState.Continuous | ExecutionState.SystemRequired;
259
260                    //Start a new log session to separate this session's events
261                    //from previous ones.
262                    LogSink sessionLog = new LogSink();
263                    task.Log.Add(sessionLog);
264                    using (new LogSession(sessionLog))
265                    {
266                        ExecuteTask(task);
267                    }
268                }
269
270                //Wait for half a minute to check for the next scheduled task.
271                schedulerInterrupt.WaitOne(30000, false);
272            }
273        }
274
275        /// <summary>
276        /// Executes the given task.
277        /// </summary>
278        /// <param name="task">The task to execute.</param>
279        private void ExecuteTask(Task task)
280        {
281            try
282            {
283                //Broadcast the task started event.
284                task.Canceled = false;
285                task.OnTaskStarted();
286
287                //Run the task
288                foreach (ErasureTarget target in task.Targets)
289                    try
290                    {
291                        UnusedSpaceTarget unusedSpaceTarget =
292                            target as UnusedSpaceTarget;
293                        FileSystemObjectTarget fileSystemObjectTarget =
294                            target as FileSystemObjectTarget;
295
296                        if (unusedSpaceTarget != null)
297                            EraseUnusedSpace(task, unusedSpaceTarget);
298                        else if (fileSystemObjectTarget != null)
299                            EraseFilesystemObject(task, fileSystemObjectTarget);
300                        else
301                            throw new ArgumentException("Unknown erasure target.");
302                    }
303                    catch (FatalException)
304                    {
305                        throw;
306                    }
307                    catch (OperationCanceledException)
308                    {
309                        throw;
310                    }
311                    catch (ThreadAbortException)
312                    {
313                    }
314                    catch (Exception e)
315                    {
316                        Logger.Log(e.Message, LogLevel.Error);
317                        BlackBox.Get().CreateReport(e);
318                    }
319            }
320            catch (FatalException e)
321            {
322                Logger.Log(e.Message, LogLevel.Fatal);
323            }
324            catch (OperationCanceledException e)
325            {
326                Logger.Log(e.Message, LogLevel.Fatal);
327            }
328            catch (ThreadAbortException)
329            {
330                //Do nothing. The exception will be rethrown after this block
331                //is executed. This is here mainly to ensure that no BlackBox
332                //report is created for this exception.
333            }
334            catch (Exception e)
335            {
336                Logger.Log(e.Message, LogLevel.Error);
337                BlackBox.Get().CreateReport(e);
338            }
339            finally
340            {
341                //Allow the system to sleep again.
342                Power.ExecutionState = ExecutionState.Continuous;
343
344                //If the task is a recurring task, reschedule it since we are done.
345                if (task.Schedule is RecurringSchedule)
346                    ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
347
348                //If the task is an execute on restart task, it is only run
349                //once and can now be restored to an immediately executed task
350                if (task.Schedule == Schedule.RunOnRestart)
351                    task.Schedule = Schedule.RunNow;
352
353                //And the task finished event.
354                task.OnTaskFinished();
355
356                //Remove the actively executing task from our instance variable
357                currentTask = null;
358            }
359        }
360
361        /// <summary>
362        /// Executes a unused space erase.
363        /// </summary>
364        /// <param name="task">The task currently being executed</param>
365        /// <param name="target">The target of the unused space erase.</param>
366        private void EraseUnusedSpace(Task task, UnusedSpaceTarget target)
367        {
368            //Check for sufficient privileges to run the unused space erasure.
369            if (!Security.IsAdministrator())
370            {
371                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
372                    Environment.OSVersion.Version >= new Version(6, 0))
373                {
374                    Logger.Log(S._("The program does not have the required permissions to erase " +
375                        "the unused space on disk. Run the program as an administrator and retry " +
376                        "the operation."), LogLevel.Error);
377                }
378                else
379                {
380                    Logger.Log(S._("The program does not have the required permissions to erase " +
381                        "the unused space on disk."), LogLevel.Error);
382                }
383            }
384
385            //Check whether System Restore has any available checkpoints.
386            if (SystemRestore.GetInstances().Count != 0)
387            {
388                Logger.Log(S._("The drive {0} has System Restore or Volume Shadow Copies " +
389                    "enabled. This may allow copies of files stored on the disk to be recovered " +
390                    "and pose a security concern.", target.Drive), LogLevel.Warning);
391            }
392           
393            //If the user is under disk quotas, log a warning message
394            if (VolumeInfo.FromMountPoint(target.Drive).HasQuota)
395                Logger.Log(S._("The drive {0} has disk quotas active. This will prevent the " +
396                    "complete erasure of unused space and may pose a security concern.",
397                    target.Drive), LogLevel.Warning);
398
399            //Get the erasure method if the user specified he wants the default.
400            ErasureMethod method = target.Method;
401
402            //Make a folder to dump our temporary files in
403            DirectoryInfo info = new DirectoryInfo(target.Drive);
404            VolumeInfo volInfo = VolumeInfo.FromMountPoint(target.Drive);
405            FileSystem fsManager = FileSystemManager.Get(volInfo);
406
407            //Start sampling the speed of the task.
408            SteppedProgressManager progress = new SteppedProgressManager();
409            target.Progress = progress;
410            task.Progress.Steps.Add(new SteppedProgressManagerStep(
411                progress, 1.0f / task.Targets.Count));
412
413            //Erase the cluster tips of every file on the drive.
414            if (target.EraseClusterTips)
415            {
416                //Define the callback handlers
417                ProgressManager tipSearch = new ProgressManager();
418                progress.Steps.Add(new SteppedProgressManagerStep(tipSearch, 
419                    0.0f, S._("Searching for files' cluster tips...")));
420                tipSearch.Total = 1;
421                ClusterTipsSearchProgress searchProgress = delegate(string path)
422                    {
423                        if (currentTask.Canceled)
424                            throw new OperationCanceledException(S._("The task was cancelled."));
425
426                        task.OnProgressChanged(target,
427                            new ProgressChangedEventArgs(tipSearch,
428                                new TaskProgressChangedEventArgs(path, 0, 0)));
429                    };
430
431                ProgressManager tipProgress = new ProgressManager();
432                progress.Steps.Add(new SteppedProgressManagerStep(tipProgress, 0.1f,
433                    S._("Erasing cluster tips...")));
434                ClusterTipsEraseProgress eraseProgress =
435                    delegate(int currentFile, int totalFiles, string currentFilePath)
436                    {
437                        tipSearch.MarkComplete();
438                        tipProgress.Total = totalFiles;
439                        tipProgress.Completed = currentFile;
440                        task.OnProgressChanged(target,
441                            new ProgressChangedEventArgs(tipProgress,
442                                new TaskProgressChangedEventArgs(currentFilePath, 0, 0)));
443
444                        if (currentTask.Canceled)
445                            throw new OperationCanceledException(S._("The task was cancelled."));
446                    };
447
448                //Start counting statistics
449                fsManager.EraseClusterTips(VolumeInfo.FromMountPoint(target.Drive),
450                    method, searchProgress, eraseProgress);
451                tipProgress.MarkComplete();
452            }
453
454            bool lowDiskSpaceNotifications = Shell.LowDiskSpaceNotificationsEnabled;
455            info = info.CreateSubdirectory(Path.GetFileName(
456                FileSystem.GenerateRandomFileName(info, 18)));
457            try
458            {
459                //Set the folder's compression flag off since we want to use as much
460                //space as possible
461                if (info.IsCompressed())
462                    info.Uncompress();
463
464                //Disable the low disk space notifications
465                Shell.LowDiskSpaceNotificationsEnabled = false;
466
467                ProgressManager mainProgress = new ProgressManager();
468                progress.Steps.Add(new SteppedProgressManagerStep(mainProgress,
469                    target.EraseClusterTips ? 0.8f : 0.9f, S._("Erasing unused space...")));
470
471                //Continue creating files while there is free space.
472                while (volInfo.AvailableFreeSpace > 0)
473                {
474                    //Generate a non-existant file name
475                    string currFile = FileSystem.GenerateRandomFileName(info, 18);
476
477                    //Create the stream
478                    using (FileStream stream = new FileStream(currFile, FileMode.CreateNew,
479                        FileAccess.Write, FileShare.None, 8, FileOptions.WriteThrough))
480                    {
481                        //Set the length of the file to be the amount of free space left
482                        //or the maximum size of one of these dumps.
483                        mainProgress.Total = mainProgress.Completed +
484                            method.CalculateEraseDataSize(null, volInfo.AvailableFreeSpace);
485                        long streamLength = Math.Min(ErasureMethod.FreeSpaceFileUnit,
486                            mainProgress.Total);
487
488                        //Handle IO exceptions gracefully, because the filesystem
489                        //may require more space than demanded by us for file allocation.
490                        while (true)
491                            try
492                            {
493                                stream.SetLength(streamLength);
494                                break;
495                            }
496                            catch (IOException)
497                            {
498                                if (streamLength > volInfo.ClusterSize)
499                                    streamLength -= volInfo.ClusterSize;
500                                else
501                                    throw;
502                            }
503
504                        //Then run the erase task
505                        method.Erase(stream, long.MaxValue,
506                            PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng),
507                            delegate(long lastWritten, long totalData, int currentPass)
508                            {
509                                mainProgress.Completed += lastWritten;
510                                task.OnProgressChanged(target,
511                                    new ProgressChangedEventArgs(mainProgress,
512                                        new TaskProgressChangedEventArgs(target.Drive, currentPass, method.Passes)));
513
514                                if (currentTask.Canceled)
515                                    throw new OperationCanceledException(S._("The task was cancelled."));
516                            }
517                        );
518                    }
519                }
520
521                //Mark the main bulk of the progress as complete
522                mainProgress.MarkComplete();
523
524                //Erase old resident file system table files
525                ProgressManager residentProgress = new ProgressManager();
526                progress.Steps.Add(new SteppedProgressManagerStep(residentProgress,
527                    0.05f, S._("Old resident file system table files")));
528                fsManager.EraseOldFileSystemResidentFiles(volInfo, info, method,
529                    delegate(int currentFile, int totalFiles)
530                    {
531                        residentProgress.Completed = currentFile;
532                        residentProgress.Total = totalFiles;
533                        task.OnProgressChanged(target,
534                            new ProgressChangedEventArgs(residentProgress,
535                                new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
536
537                        if (currentTask.Canceled)
538                            throw new OperationCanceledException(S._("The task was cancelled."));
539                    }
540                );
541
542                residentProgress.MarkComplete();
543            }
544            finally
545            {
546                //Remove the folder holding all our temporary files.
547                ProgressManager tempFiles = new ProgressManager();
548                progress.Steps.Add(new SteppedProgressManagerStep(tempFiles,
549                    0.0f, S._("Removing temporary files...")));
550                task.OnProgressChanged(target, new ProgressChangedEventArgs(tempFiles,
551                    new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
552                fsManager.DeleteFolder(info);
553                tempFiles.Completed = tempFiles.Total;
554
555                //Reset the low disk space notifications
556                Shell.LowDiskSpaceNotificationsEnabled = lowDiskSpaceNotifications;
557            }
558
559            //Then clean the old file system entries
560            ProgressManager structureProgress = new ProgressManager();
561            progress.Steps.Add(new SteppedProgressManagerStep(structureProgress,
562                0.05f, S._("Erasing unused directory structures...")));
563            fsManager.EraseDirectoryStructures(volInfo,
564                delegate(int currentFile, int totalFiles)
565                {
566                    if (currentTask.Canceled)
567                        throw new OperationCanceledException(S._("The task was cancelled."));
568
569                    //Compute the progress
570                    structureProgress.Total = totalFiles;
571                    structureProgress.Completed = currentFile;
572
573                    //Set the event parameters, then broadcast the progress event.
574                    task.OnProgressChanged(target,
575                        new ProgressChangedEventArgs(structureProgress,
576                            new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
577                }
578            );
579
580            structureProgress.MarkComplete();
581            target.Progress = null;
582        }
583
584        /// <summary>
585        /// Erases a file or folder on the volume.
586        /// </summary>
587        /// <param name="task">The task currently being processed.</param>
588        /// <param name="target">The target of the erasure.</param>
589        /// <param name="progress">The progress manager for the current task.</param>
590        private void EraseFilesystemObject(Task task, FileSystemObjectTarget target)
591        {
592            //Retrieve the list of files to erase.
593            long dataTotal = 0;
594            List<string> paths = target.GetPaths(out dataTotal);
595
596            //Get the erasure method if the user specified he wants the default.
597            ErasureMethod method = target.Method;
598
599            //Set the event's current target status.
600            SteppedProgressManager progress = new SteppedProgressManager();
601            target.Progress = progress;
602            task.Progress.Steps.Add(new SteppedProgressManagerStep(progress, 1.0f / task.Targets.Count));
603
604            //Iterate over every path, and erase the path.
605            for (int i = 0; i < paths.Count; ++i)
606            {
607                //Update the task progress
608                ProgressManager step = new ProgressManager();
609                progress.Steps.Add(new SteppedProgressManagerStep(step,
610                    1.0f / paths.Count, S._("Erasing files...")));
611                task.OnProgressChanged(target,
612                    new ProgressChangedEventArgs(step,
613                        new TaskProgressChangedEventArgs(paths[i], 0, method.Passes)));
614
615                //Check that the file exists - we do not want to bother erasing nonexistant files
616                StreamInfo info = new StreamInfo(paths[i]);
617                if (!info.Exists)
618                {
619                    Logger.Log(S._("The file {0} was not erased as the file does not exist.",
620                        paths[i]), LogLevel.Notice);
621                    continue;
622                }
623
624                //Get the filesystem provider to handle the secure file erasures
625                FileSystem fsManager = FileSystemManager.Get(
626                    VolumeInfo.FromMountPoint(info.DirectoryName));
627
628                bool isReadOnly = false;
629               
630                try
631                {
632                    //Remove the read-only flag, if it is set.
633                    if (isReadOnly = info.IsReadOnly)
634                        info.IsReadOnly = false;
635
636                    //Make sure the file does not have any attributes which may affect
637                    //the erasure process
638                    if ((info.Attributes & FileAttributes.Compressed) != 0 || 
639                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
640                        (info.Attributes & FileAttributes.SparseFile) != 0)
641                    {
642                        //Log the error
643                        Logger.Log(S._("The file {0} could not be erased because the file was " +
644                            "either compressed, encrypted or a sparse file.", info.FullName),
645                            LogLevel.Error);
646                        continue;
647                    }
648
649                    fsManager.EraseFileSystemObject(info, method,
650                        delegate(long lastWritten, long totalData, int currentPass)
651                        {
652                            if (currentTask.Canceled)
653                                throw new OperationCanceledException(S._("The task was cancelled."));
654
655                            step.Total = totalData;
656                            step.Completed += lastWritten;
657                            task.OnProgressChanged(target,
658                                new ProgressChangedEventArgs(step,
659                                    new TaskProgressChangedEventArgs(info.FullName, currentPass, method.Passes)));
660                        });
661
662                    //Remove the file.
663                    FileInfo fileInfo = info.File;
664                    if (fileInfo != null)
665                        fsManager.DeleteFile(fileInfo);
666                    step.MarkComplete();
667                }
668                catch (UnauthorizedAccessException)
669                {
670                    Logger.Log(S._("The file {0} could not be erased because the file's " +
671                        "permissions prevent access to the file.", info.FullName), LogLevel.Error);
672                }
673                catch (IOException)
674                {
675                    if (System.Runtime.InteropServices.Marshal.GetLastWin32Error() ==
676                        Win32ErrorCode.SharingViolation)
677                    {
678                        if (!ManagerLibrary.Settings.ForceUnlockLockedFiles)
679                            throw;
680
681                        List<System.Diagnostics.Process> processes =
682                            new List<System.Diagnostics.Process>();
683                        foreach (OpenHandle handle in OpenHandle.Items)
684                            if (handle.Path == paths[i])
685                                processes.Add(System.Diagnostics.Process.GetProcessById(handle.ProcessId));
686
687                        string lockedBy = null;
688                        if (processes.Count > 0)
689                        {
690                            StringBuilder processStr = new StringBuilder();
691                            foreach (System.Diagnostics.Process process in processes)
692                            {
693                                try
694                                {
695                                    processStr.AppendFormat(System.Globalization.CultureInfo.InvariantCulture,
696                                        "{0}, ", process.MainModule.FileName);
697                                }
698                                catch (System.ComponentModel.Win32Exception)
699                                {
700                                }
701                            }
702
703                            lockedBy = S._("(locked by {0})", processStr.ToString().Remove(processStr.Length - 2));
704                        }
705
706                        Logger.Log(S._("Could not force closure of file \"{0}\" {1}", paths[i],
707                            lockedBy == null ? string.Empty : lockedBy).Trim(), LogLevel.Error);
708                    }
709                    else
710                        throw;
711                }
712                finally
713                {
714                    //Re-set the read-only flag if the file exists (i.e. there was an error)
715                    if (isReadOnly && info.Exists && !info.IsReadOnly)
716                        info.IsReadOnly = isReadOnly;
717                }
718            }
719
720            //If the user requested a folder removal, do it.
721            if ((target is FolderTarget) && Directory.Exists(target.Path))
722            {
723                ProgressManager step = new ProgressManager();
724                progress.Steps.Add(new SteppedProgressManagerStep(step,
725                    0.0f, S._("Removing folders...")));
726               
727                //Remove all subfolders which are empty.
728                FolderTarget fldr = (FolderTarget)target;
729                FileSystem fsManager = FileSystemManager.Get(VolumeInfo.FromMountPoint(fldr.Path));
730                Action<DirectoryInfo> eraseEmptySubFolders = null;
731                eraseEmptySubFolders = delegate(DirectoryInfo info)
732                {
733                     foreach (DirectoryInfo subDir in info.GetDirectories())
734                         eraseEmptySubFolders(subDir);
735                     task.OnProgressChanged(target,
736                         new ProgressChangedEventArgs(step,
737                             new TaskProgressChangedEventArgs(info.FullName, 0, 0)));
738
739                     FileSystemInfo[] files = info.GetFileSystemInfos();
740                     if (files.Length == 0)
741                         fsManager.DeleteFolder(info);
742                };
743                eraseEmptySubFolders(new DirectoryInfo(fldr.Path));
744
745                if (fldr.DeleteIfEmpty)
746                {
747                    DirectoryInfo info = new DirectoryInfo(fldr.Path);
748                    task.OnProgressChanged(target,
749                        new ProgressChangedEventArgs(step,
750                            new TaskProgressChangedEventArgs(info.FullName, 0, 0)));
751                    fsManager.DeleteFolder(info);
752                }
753            }
754
755            //If the user was erasing the recycle bin, clear the bin.
756            if (target is RecycleBinTarget)
757            {
758                ProgressManager step = new ProgressManager();
759                progress.Steps.Add(new SteppedProgressManagerStep(step,
760                    0.0f, S._("Emptying recycle bin...")));
761                task.OnProgressChanged(target,
762                    new ProgressChangedEventArgs(step,
763                        new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
764
765                RecycleBin.Empty(EmptyRecycleBinOptions.NoConfirmation |
766                    EmptyRecycleBinOptions.NoProgressUI | EmptyRecycleBinOptions.NoSound);
767            }
768
769            target.Progress = null;
770        }
771
772        /// <summary>
773        /// The thread object.
774        /// </summary>
775        private Thread thread;
776
777        /// <summary>
778        /// The lock preventing concurrent access for the tasks list and the
779        /// tasks queue.
780        /// </summary>
781        private object tasksLock = new object();
782
783        /// <summary>
784        /// The queue of tasks. This queue is executed when the first element's
785        /// timestamp (the key) has been past. This list assumes that all tasks
786        /// are sorted by timestamp, smallest one first.
787        /// </summary>
788        private SortedList<DateTime, List<Task>> scheduledTasks =
789            new SortedList<DateTime, List<Task>>();
790
791        /// <summary>
792        /// The task list associated with this executor instance.
793        /// </summary>
794        private DirectExecutorTasksCollection tasks;
795
796        /// <summary>
797        /// The currently executing task.
798        /// </summary>
799        Task currentTask;
800
801        /// <summary>
802        /// An automatically reset event allowing the addition of new tasks to
803        /// interrupt the thread's sleeping state waiting for the next recurring
804        /// task to be due.
805        /// </summary>
806        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
807
808        private class DirectExecutorTasksCollection : ExecutorTasksCollection
809        {
810            /// <summary>
811            /// Constructor.
812            /// </summary>
813            /// <param name="executor">The <see cref="DirectExecutor"/> object owning
814            /// this list.</param>
815            public DirectExecutorTasksCollection(DirectExecutor executor)
816                : base(executor)
817            {
818            }
819
820            #region IList<Task> Members
821            public override int IndexOf(Task item)
822            {
823                return list.IndexOf(item);
824            }
825
826            public override void Insert(int index, Task item)
827            {
828                item.Executor = Owner;
829                lock (list)
830                    list.Insert(index, item);
831
832                //Call all the event handlers who registered to be notified of tasks
833                //being added.
834                Owner.OnTaskAdded(new TaskEventArgs(item));
835
836                //If the task is scheduled to run now, break the waiting thread and
837                //run it immediately
838                if (item.Schedule == Schedule.RunNow)
839                {
840                    Owner.QueueTask(item);
841                }
842                //If the task is scheduled, add the next execution time to the list
843                //of schduled tasks.
844                else if (item.Schedule != Schedule.RunOnRestart)
845                {
846                    Owner.ScheduleTask(item);
847                }
848            }
849
850            public override void RemoveAt(int index)
851            {
852                lock (list)
853                {
854                    Task task = list[index];
855                    task.Cancel();
856                    task.Executor = null;
857                    list.RemoveAt(index);
858
859                    //Call all event handlers registered to be notified of task deletions.
860                    Owner.OnTaskDeleted(new TaskEventArgs(task));
861                }
862            }
863
864            public override Task this[int index]
865            {
866                get
867                {
868                    lock (list)
869                        return list[index];
870                }
871                set
872                {
873                    lock (list)
874                        list[index] = value;
875                }
876            }
877            #endregion
878
879            #region ICollection<Task> Members
880            public override void Add(Task item)
881            {
882                Insert(Count, item);
883            }
884
885            public override void Clear()
886            {
887                foreach (Task task in list)
888                    Remove(task);
889            }
890
891            public override bool Contains(Task item)
892            {
893                lock (list)
894                    return list.Contains(item);
895            }
896
897            public override void CopyTo(Task[] array, int arrayIndex)
898            {
899                lock (list)
900                    list.CopyTo(array, arrayIndex);
901            }
902
903            public override int Count
904            {
905                get
906                {
907                    lock (list)
908                        return list.Count;
909                }
910            }
911
912            public override bool Remove(Task item)
913            {
914                lock (list)
915                {
916                    int index = list.IndexOf(item);
917                    if (index < 0)
918                        return false;
919
920                    RemoveAt(index);
921                }
922
923                return true;
924            }
925            #endregion
926
927            #region IEnumerable<Task> Members
928            public override IEnumerator<Task> GetEnumerator()
929            {
930                return list.GetEnumerator();
931            }
932            #endregion
933
934            public override void SaveToStream(Stream stream)
935            {
936                lock (list)
937                    new BinaryFormatter().Serialize(stream, list);
938            }
939
940            public override void LoadFromStream(Stream stream)
941            {
942                //Load the list into the dictionary
943                StreamingContext context = new StreamingContext(
944                    StreamingContextStates.All, Owner);
945                BinaryFormatter formatter = new BinaryFormatter(null, context);
946                List<Task> deserialised = (List<Task>)formatter.Deserialize(stream);
947                list.AddRange(deserialised);
948
949                foreach (Task task in deserialised)
950                {
951                    Owner.OnTaskAdded(new TaskEventArgs(task));
952                    if (task.Schedule is RecurringSchedule)
953                        Owner.ScheduleTask(task);
954                }
955            }
956
957            /// <summary>
958            /// The data store for this object.
959            /// </summary>
960            private List<Task> list = new List<Task>();
961        }
962    }
963}
Note: See TracBrowser for help on using the repository browser.