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

Revision 1772, 28.7 KB checked in by lowjoel, 4 years ago (diff)

Allow all IDisposable patterns to be reentrant. Addresses #275: Code Review

  • 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 |
259                            ExecutionState.SystemRequired;
260
261                    //Start a new log session to separate this session's events
262                    //from previous ones.
263                    task.Log.Entries.NewSession();
264
265                    try
266                    {
267                        //Broadcast the task started event.
268                        task.Canceled = false;
269                        task.OnTaskStarted();
270
271                        //Run the task
272                        foreach (ErasureTarget target in task.Targets)
273                            try
274                            {
275                                UnusedSpaceTarget unusedSpaceTarget =
276                                    target as UnusedSpaceTarget;
277                                FileSystemObjectTarget fileSystemObjectTarget =
278                                    target as FileSystemObjectTarget;
279
280                                if (unusedSpaceTarget != null)
281                                    EraseUnusedSpace(task, unusedSpaceTarget);
282                                else if (fileSystemObjectTarget != null)
283                                    EraseFilesystemObject(task, fileSystemObjectTarget);
284                                else
285                                    throw new ArgumentException("Unknown erasure target.");
286                            }
287                            catch (FatalException)
288                            {
289                                throw;
290                            }
291                            catch (OperationCanceledException)
292                            {
293                                throw;
294                            }
295                            catch (ThreadAbortException)
296                            {
297                            }
298                            catch (Exception e)
299                            {
300                                task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
301                                BlackBox.Get().CreateReport(e);
302                            }
303                    }
304                    catch (FatalException e)
305                    {
306                        task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Fatal));
307                    }
308                    catch (OperationCanceledException e)
309                    {
310                        task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Fatal));
311                    }
312                    catch (ThreadAbortException)
313                    {
314                        //Do nothing. The exception will be rethrown after this block
315                        //is executed. This is here mainly to ensure that no BlackBox
316                        //report is created for this exception.
317                    }
318                    catch (Exception e)
319                    {
320                        task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
321                        BlackBox.Get().CreateReport(e);
322                    }
323                    finally
324                    {
325                        //Allow the system to sleep again.
326                        Power.ExecutionState = ExecutionState.Continuous;
327
328                        //If the task is a recurring task, reschedule it since we are done.
329                        if (task.Schedule is RecurringSchedule)
330                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
331
332                        //If the task is an execute on restart task, it is only run
333                        //once and can now be restored to an immediately executed task
334                        if (task.Schedule == Schedule.RunOnRestart)
335                            task.Schedule = Schedule.RunNow;
336
337                        //And the task finished event.
338                        task.OnTaskFinished();
339
340                        //Remove the actively executing task from our instance variable
341                        currentTask = null;
342                    }
343                }
344
345                //Wait for half a minute to check for the next scheduled task.
346                schedulerInterrupt.WaitOne(30000, false);
347            }
348        }
349
350        /// <summary>
351        /// Executes a unused space erase.
352        /// </summary>
353        /// <param name="task">The task currently being executed</param>
354        /// <param name="target">The target of the unused space erase.</param>
355        private void EraseUnusedSpace(Task task, UnusedSpaceTarget target)
356        {
357            //Check for sufficient privileges to run the unused space erasure.
358            if (!Security.IsAdministrator())
359            {
360                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
361                    Environment.OSVersion.Version >= new Version(6, 0))
362                {
363                    task.Log.LastSessionEntries.Add(new LogEntry(S._("The program does not have " +
364                        "the required permissions to erase the unused space on disk. Run the " +
365                        "program as an administrator and retry the operation."), LogLevel.Error));
366                }
367                else
368                {
369                    task.Log.LastSessionEntries.Add(new LogEntry(S._("The program does not have " +
370                        "the required permissions to erase the unused space on disk."),
371                        LogLevel.Error));
372                }
373            }
374
375            //Check whether System Restore has any available checkpoints.
376            if (SystemRestore.GetInstances().Count != 0)
377            {
378                task.Log.LastSessionEntries.Add(new LogEntry(S._("The drive {0} has System " +
379                    "Restore or Volume Shadow Copies enabled. This may allow copies of files " +
380                    "stored on the disk to be recovered and pose a security concern.",
381                    target.Drive), LogLevel.Warning));
382            }
383           
384            //If the user is under disk quotas, log a warning message
385            if (VolumeInfo.FromMountPoint(target.Drive).HasQuota)
386                task.Log.LastSessionEntries.Add(new LogEntry(S._("The drive {0} has disk quotas " +
387                    "active. This will prevent the complete erasure of unused space and may pose " +
388                    "a security concern.", target.Drive), LogLevel.Warning));
389
390            //Get the erasure method if the user specified he wants the default.
391            ErasureMethod method = target.Method;
392
393            //Make a folder to dump our temporary files in
394            DirectoryInfo info = new DirectoryInfo(target.Drive);
395            VolumeInfo volInfo = VolumeInfo.FromMountPoint(target.Drive);
396            FileSystem fsManager = FileSystemManager.Get(volInfo);
397
398            //Start sampling the speed of the task.
399            SteppedProgressManager progress = new SteppedProgressManager();
400            target.Progress = progress;
401            task.Progress.Steps.Add(new SteppedProgressManagerStep(
402                progress, 1.0f / task.Targets.Count));
403
404            //Erase the cluster tips of every file on the drive.
405            if (target.EraseClusterTips)
406            {
407                //Define the callback handlers
408                ProgressManager tipSearch = new ProgressManager();
409                progress.Steps.Add(new SteppedProgressManagerStep(tipSearch, 
410                    0.0f, S._("Searching for files' cluster tips...")));
411                tipSearch.Total = 1;
412                ClusterTipsSearchProgress searchProgress = delegate(string path)
413                    {
414                        if (currentTask.Canceled)
415                            throw new OperationCanceledException(S._("The task was cancelled."));
416
417                        task.OnProgressChanged(target,
418                            new ProgressChangedEventArgs(tipSearch,
419                                new TaskProgressChangedEventArgs(path, 0, 0)));
420                    };
421
422                ProgressManager tipProgress = new ProgressManager();
423                progress.Steps.Add(new SteppedProgressManagerStep(tipProgress, 0.1f,
424                    S._("Erasing cluster tips...")));
425                ClusterTipsEraseProgress eraseProgress =
426                    delegate(int currentFile, int totalFiles, string currentFilePath)
427                    {
428                        tipSearch.MarkComplete();
429                        tipProgress.Total = totalFiles;
430                        tipProgress.Completed = currentFile;
431                        task.OnProgressChanged(target,
432                            new ProgressChangedEventArgs(tipProgress,
433                                new TaskProgressChangedEventArgs(currentFilePath, 0, 0)));
434
435                        if (currentTask.Canceled)
436                            throw new OperationCanceledException(S._("The task was cancelled."));
437                    };
438
439                //Start counting statistics
440                fsManager.EraseClusterTips(VolumeInfo.FromMountPoint(target.Drive),
441                    method, task.Log, searchProgress, eraseProgress);
442                tipProgress.MarkComplete();
443            }
444
445            bool lowDiskSpaceNotifications = Shell.LowDiskSpaceNotificationsEnabled;
446            info = info.CreateSubdirectory(Path.GetFileName(
447                FileSystem.GenerateRandomFileName(info, 18)));
448            try
449            {
450                //Set the folder's compression flag off since we want to use as much
451                //space as possible
452                if (info.IsCompressed())
453                    info.Uncompress();
454
455                //Disable the low disk space notifications
456                Shell.LowDiskSpaceNotificationsEnabled = false;
457
458                ProgressManager mainProgress = new ProgressManager();
459                progress.Steps.Add(new SteppedProgressManagerStep(mainProgress,
460                    target.EraseClusterTips ? 0.8f : 0.9f, S._("Erasing unused space...")));
461
462                //Continue creating files while there is free space.
463                while (volInfo.AvailableFreeSpace > 0)
464                {
465                    //Generate a non-existant file name
466                    string currFile = FileSystem.GenerateRandomFileName(info, 18);
467
468                    //Create the stream
469                    using (FileStream stream = new FileStream(currFile, FileMode.CreateNew,
470                        FileAccess.Write, FileShare.None, 8, FileOptions.WriteThrough))
471                    {
472                        //Set the length of the file to be the amount of free space left
473                        //or the maximum size of one of these dumps.
474                        mainProgress.Total = mainProgress.Completed +
475                            method.CalculateEraseDataSize(null, volInfo.AvailableFreeSpace);
476                        long streamLength = Math.Min(ErasureMethod.FreeSpaceFileUnit,
477                            mainProgress.Total);
478
479                        //Handle IO exceptions gracefully, because the filesystem
480                        //may require more space than demanded by us for file allocation.
481                        while (true)
482                            try
483                            {
484                                stream.SetLength(streamLength);
485                                break;
486                            }
487                            catch (IOException)
488                            {
489                                if (streamLength > volInfo.ClusterSize)
490                                    streamLength -= volInfo.ClusterSize;
491                                else
492                                    throw;
493                            }
494
495                        //Then run the erase task
496                        method.Erase(stream, long.MaxValue,
497                            PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng),
498                            delegate(long lastWritten, long totalData, int currentPass)
499                            {
500                                mainProgress.Completed += lastWritten;
501                                task.OnProgressChanged(target,
502                                    new ProgressChangedEventArgs(mainProgress,
503                                        new TaskProgressChangedEventArgs(target.Drive, currentPass, method.Passes)));
504
505                                if (currentTask.Canceled)
506                                    throw new OperationCanceledException(S._("The task was cancelled."));
507                            }
508                        );
509                    }
510                }
511
512                //Mark the main bulk of the progress as complete
513                mainProgress.MarkComplete();
514
515                //Erase old resident file system table files
516                ProgressManager residentProgress = new ProgressManager();
517                progress.Steps.Add(new SteppedProgressManagerStep(residentProgress,
518                    0.05f, S._("Old resident file system table files")));
519                fsManager.EraseOldFileSystemResidentFiles(volInfo, info, method,
520                    delegate(int currentFile, int totalFiles)
521                    {
522                        residentProgress.Completed = currentFile;
523                        residentProgress.Total = totalFiles;
524                        task.OnProgressChanged(target,
525                            new ProgressChangedEventArgs(residentProgress,
526                                new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
527
528                        if (currentTask.Canceled)
529                            throw new OperationCanceledException(S._("The task was cancelled."));
530                    }
531                );
532
533                residentProgress.MarkComplete();
534            }
535            finally
536            {
537                //Remove the folder holding all our temporary files.
538                ProgressManager tempFiles = new ProgressManager();
539                progress.Steps.Add(new SteppedProgressManagerStep(tempFiles,
540                    0.0f, S._("Removing temporary files...")));
541                task.OnProgressChanged(target, new ProgressChangedEventArgs(tempFiles,
542                    new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
543                fsManager.DeleteFolder(info);
544                tempFiles.Completed = tempFiles.Total;
545
546                //Reset the low disk space notifications
547                Shell.LowDiskSpaceNotificationsEnabled = lowDiskSpaceNotifications;
548            }
549
550            //Then clean the old file system entries
551            ProgressManager structureProgress = new ProgressManager();
552            progress.Steps.Add(new SteppedProgressManagerStep(structureProgress,
553                0.05f, S._("Erasing unused directory structures...")));
554            fsManager.EraseDirectoryStructures(volInfo,
555                delegate(int currentFile, int totalFiles)
556                {
557                    if (currentTask.Canceled)
558                        throw new OperationCanceledException(S._("The task was cancelled."));
559
560                    //Compute the progress
561                    structureProgress.Total = totalFiles;
562                    structureProgress.Completed = currentFile;
563
564                    //Set the event parameters, then broadcast the progress event.
565                    task.OnProgressChanged(target,
566                        new ProgressChangedEventArgs(structureProgress,
567                            new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
568                }
569            );
570
571            structureProgress.MarkComplete();
572            target.Progress = null;
573        }
574
575        /// <summary>
576        /// Erases a file or folder on the volume.
577        /// </summary>
578        /// <param name="task">The task currently being processed.</param>
579        /// <param name="target">The target of the erasure.</param>
580        /// <param name="progress">The progress manager for the current task.</param>
581        private void EraseFilesystemObject(Task task, FileSystemObjectTarget target)
582        {
583            //Retrieve the list of files to erase.
584            long dataTotal = 0;
585            List<string> paths = target.GetPaths(out dataTotal);
586
587            //Get the erasure method if the user specified he wants the default.
588            ErasureMethod method = target.Method;
589
590            //Set the event's current target status.
591            SteppedProgressManager progress = new SteppedProgressManager();
592            target.Progress = progress;
593            task.Progress.Steps.Add(new SteppedProgressManagerStep(progress, 1.0f / task.Targets.Count));
594
595            //Iterate over every path, and erase the path.
596            for (int i = 0; i < paths.Count; ++i)
597            {
598                //Update the task progress
599                ProgressManager step = new ProgressManager();
600                progress.Steps.Add(new SteppedProgressManagerStep(step,
601                    1.0f / paths.Count, S._("Erasing files...")));
602                task.OnProgressChanged(target,
603                    new ProgressChangedEventArgs(step,
604                        new TaskProgressChangedEventArgs(paths[i], 0, method.Passes)));
605
606                //Check that the file exists - we do not want to bother erasing nonexistant files
607                StreamInfo info = new StreamInfo(paths[i]);
608                if (!info.Exists)
609                {
610                    task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} was not erased " +
611                        "as the file does not exist.", paths[i]), LogLevel.Notice));
612                    continue;
613                }
614
615                //Get the filesystem provider to handle the secure file erasures
616                FileSystem fsManager = FileSystemManager.Get(
617                    VolumeInfo.FromMountPoint(info.DirectoryName));
618
619                bool isReadOnly = false;
620               
621                try
622                {
623                    //Remove the read-only flag, if it is set.
624                    if (isReadOnly = info.IsReadOnly)
625                        info.IsReadOnly = false;
626
627                    //Make sure the file does not have any attributes which may affect
628                    //the erasure process
629                    if ((info.Attributes & FileAttributes.Compressed) != 0 || 
630                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
631                        (info.Attributes & FileAttributes.SparseFile) != 0)
632                    {
633                        //Log the error
634                        task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could " +
635                            "not be erased because the file was either compressed, encrypted or " +
636                            "a sparse file.", info.FullName), LogLevel.Error));
637                        continue;
638                    }
639
640                    fsManager.EraseFileSystemObject(info, method,
641                        delegate(long lastWritten, long totalData, int currentPass)
642                        {
643                            if (currentTask.Canceled)
644                                throw new OperationCanceledException(S._("The task was cancelled."));
645
646                            step.Total = totalData;
647                            step.Completed += lastWritten;
648                            task.OnProgressChanged(target,
649                                new ProgressChangedEventArgs(step,
650                                    new TaskProgressChangedEventArgs(info.FullName, currentPass, method.Passes)));
651                        });
652
653                    //Remove the file.
654                    FileInfo fileInfo = info.File;
655                    if (fileInfo != null)
656                        fsManager.DeleteFile(fileInfo);
657                    step.MarkComplete();
658                }
659                catch (UnauthorizedAccessException)
660                {
661                    task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could not " +
662                        "be erased because the file's permissions prevent access to the file.",
663                        info.FullName), LogLevel.Error));
664                }
665                catch (IOException)
666                {
667                    if (System.Runtime.InteropServices.Marshal.GetLastWin32Error() ==
668                        Win32ErrorCode.SharingViolation)
669                    {
670                        if (!ManagerLibrary.Settings.ForceUnlockLockedFiles)
671                            throw;
672
673                        List<System.Diagnostics.Process> processes =
674                            new List<System.Diagnostics.Process>();
675                        foreach (OpenHandle handle in OpenHandle.Items)
676                            if (handle.Path == paths[i])
677                                processes.Add(System.Diagnostics.Process.GetProcessById(handle.ProcessId));
678
679                        string lockedBy = null;
680                        if (processes.Count > 0)
681                        {
682                            StringBuilder processStr = new StringBuilder();
683                            foreach (System.Diagnostics.Process process in processes)
684                            {
685                                try
686                                {
687                                    processStr.AppendFormat(System.Globalization.CultureInfo.InvariantCulture,
688                                        "{0}, ", process.MainModule.FileName);
689                                }
690                                catch (System.ComponentModel.Win32Exception)
691                                {
692                                }
693                            }
694
695                            lockedBy = S._("(locked by {0})", processStr.ToString().Remove(processStr.Length - 2));
696                        }
697
698                        task.Log.LastSessionEntries.Add(new LogEntry(S._(
699                            "Could not force closure of file \"{0}\" {1}", paths[i],
700                            lockedBy == null ? string.Empty : lockedBy).Trim(), LogLevel.Error));
701                    }
702                    else
703                        throw;
704                }
705                finally
706                {
707                    //Re-set the read-only flag if the file exists (i.e. there was an error)
708                    if (isReadOnly && info.Exists && !info.IsReadOnly)
709                        info.IsReadOnly = isReadOnly;
710                }
711            }
712
713            //If the user requested a folder removal, do it.
714            if ((target is FolderTarget) && Directory.Exists(target.Path))
715            {
716                ProgressManager step = new ProgressManager();
717                progress.Steps.Add(new SteppedProgressManagerStep(step,
718                    0.0f, S._("Removing folders...")));
719               
720                //Remove all subfolders which are empty.
721                FolderTarget fldr = (FolderTarget)target;
722                FileSystem fsManager = FileSystemManager.Get(VolumeInfo.FromMountPoint(fldr.Path));
723                Action<DirectoryInfo> eraseEmptySubFolders = null;
724                eraseEmptySubFolders = delegate(DirectoryInfo info)
725                {
726                     foreach (DirectoryInfo subDir in info.GetDirectories())
727                         eraseEmptySubFolders(subDir);
728                     task.OnProgressChanged(target,
729                         new ProgressChangedEventArgs(step,
730                             new TaskProgressChangedEventArgs(info.FullName, 0, 0)));
731
732                     FileSystemInfo[] files = info.GetFileSystemInfos();
733                     if (files.Length == 0)
734                         fsManager.DeleteFolder(info);
735                };
736                eraseEmptySubFolders(new DirectoryInfo(fldr.Path));
737
738                if (fldr.DeleteIfEmpty)
739                {
740                    DirectoryInfo info = new DirectoryInfo(fldr.Path);
741                    task.OnProgressChanged(target,
742                        new ProgressChangedEventArgs(step,
743                            new TaskProgressChangedEventArgs(info.FullName, 0, 0)));
744                    fsManager.DeleteFolder(info);
745                }
746            }
747
748            //If the user was erasing the recycle bin, clear the bin.
749            if (target is RecycleBinTarget)
750            {
751                ProgressManager step = new ProgressManager();
752                progress.Steps.Add(new SteppedProgressManagerStep(step,
753                    0.0f, S._("Emptying recycle bin...")));
754                task.OnProgressChanged(target,
755                    new ProgressChangedEventArgs(step,
756                        new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
757
758                RecycleBin.Empty(EmptyRecycleBinOptions.NoConfirmation |
759                    EmptyRecycleBinOptions.NoProgressUI | EmptyRecycleBinOptions.NoSound);
760            }
761
762            target.Progress = null;
763        }
764
765        /// <summary>
766        /// The thread object.
767        /// </summary>
768        private Thread thread;
769
770        /// <summary>
771        /// The lock preventing concurrent access for the tasks list and the
772        /// tasks queue.
773        /// </summary>
774        private object tasksLock = new object();
775
776        /// <summary>
777        /// The queue of tasks. This queue is executed when the first element's
778        /// timestamp (the key) has been past. This list assumes that all tasks
779        /// are sorted by timestamp, smallest one first.
780        /// </summary>
781        private SortedList<DateTime, List<Task>> scheduledTasks =
782            new SortedList<DateTime, List<Task>>();
783
784        /// <summary>
785        /// The task list associated with this executor instance.
786        /// </summary>
787        private DirectExecutorTasksCollection tasks;
788
789        /// <summary>
790        /// The currently executing task.
791        /// </summary>
792        Task currentTask;
793
794        /// <summary>
795        /// An automatically reset event allowing the addition of new tasks to
796        /// interrupt the thread's sleeping state waiting for the next recurring
797        /// task to be due.
798        /// </summary>
799        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
800
801        private class DirectExecutorTasksCollection : ExecutorTasksCollection
802        {
803            /// <summary>
804            /// Constructor.
805            /// </summary>
806            /// <param name="executor">The <see cref="DirectExecutor"/> object owning
807            /// this list.</param>
808            public DirectExecutorTasksCollection(DirectExecutor executor)
809                : base(executor)
810            {
811            }
812
813            #region IList<Task> Members
814            public override int IndexOf(Task item)
815            {
816                return list.IndexOf(item);
817            }
818
819            public override void Insert(int index, Task item)
820            {
821                item.Executor = Owner;
822                lock (list)
823                    list.Insert(index, item);
824
825                //Call all the event handlers who registered to be notified of tasks
826                //being added.
827                Owner.OnTaskAdded(new TaskEventArgs(item));
828
829                //If the task is scheduled to run now, break the waiting thread and
830                //run it immediately
831                if (item.Schedule == Schedule.RunNow)
832                {
833                    Owner.QueueTask(item);
834                }
835                //If the task is scheduled, add the next execution time to the list
836                //of schduled tasks.
837                else if (item.Schedule != Schedule.RunOnRestart)
838                {
839                    Owner.ScheduleTask(item);
840                }
841            }
842
843            public override void RemoveAt(int index)
844            {
845                lock (list)
846                {
847                    Task task = list[index];
848                    task.Cancel();
849                    task.Executor = null;
850                    list.RemoveAt(index);
851
852                    //Call all event handlers registered to be notified of task deletions.
853                    Owner.OnTaskDeleted(new TaskEventArgs(task));
854                }
855            }
856
857            public override Task this[int index]
858            {
859                get
860                {
861                    lock (list)
862                        return list[index];
863                }
864                set
865                {
866                    lock (list)
867                        list[index] = value;
868                }
869            }
870            #endregion
871
872            #region ICollection<Task> Members
873            public override void Add(Task item)
874            {
875                Insert(Count, item);
876            }
877
878            public override void Clear()
879            {
880                foreach (Task task in list)
881                    Remove(task);
882            }
883
884            public override bool Contains(Task item)
885            {
886                lock (list)
887                    return list.Contains(item);
888            }
889
890            public override void CopyTo(Task[] array, int arrayIndex)
891            {
892                lock (list)
893                    list.CopyTo(array, arrayIndex);
894            }
895
896            public override int Count
897            {
898                get
899                {
900                    lock (list)
901                        return list.Count;
902                }
903            }
904
905            public override bool Remove(Task item)
906            {
907                lock (list)
908                {
909                    int index = list.IndexOf(item);
910                    if (index < 0)
911                        return false;
912
913                    RemoveAt(index);
914                }
915
916                return true;
917            }
918            #endregion
919
920            #region IEnumerable<Task> Members
921            public override IEnumerator<Task> GetEnumerator()
922            {
923                return list.GetEnumerator();
924            }
925            #endregion
926
927            public override void SaveToStream(Stream stream)
928            {
929                lock (list)
930                    new BinaryFormatter().Serialize(stream, list);
931            }
932
933            public override void LoadFromStream(Stream stream)
934            {
935                //Load the list into the dictionary
936                StreamingContext context = new StreamingContext(
937                    StreamingContextStates.All, Owner);
938                BinaryFormatter formatter = new BinaryFormatter(null, context);
939                List<Task> deserialised = (List<Task>)formatter.Deserialize(stream);
940                list.AddRange(deserialised);
941
942                foreach (Task task in deserialised)
943                {
944                    Owner.OnTaskAdded(new TaskEventArgs(task));
945                    if (task.Schedule is RecurringSchedule)
946                        Owner.ScheduleTask(task);
947                }
948            }
949
950            /// <summary>
951            /// The data store for this object.
952            /// </summary>
953            private List<Task> list = new List<Task>();
954        }
955    }
956}
Note: See TracBrowser for help on using the repository browser.