source: trunk/eraser6/Eraser.Manager/DirectExecutor.cs @ 1872

Revision 1872, 29.1 KB checked in by lowjoel, 4 years ago (diff)

Define a discrete SharingViolationException? which is thrown when a file is currently in use. This makes handling such errors easier.

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