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

Revision 1864, 29.2 KB checked in by lowjoel, 5 years ago (diff)

Task progress should be updated within the try-catch block since getting the length of the file may cause an IOException (e.g. file already deleted, file in use etc)

  • 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 (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                return;
385            }
386
387            //Check whether System Restore has any available checkpoints.
388            if (SystemRestore.GetInstances().Count != 0)
389            {
390                Logger.Log(S._("The drive {0} has System Restore or Volume Shadow Copies " +
391                    "enabled. This may allow copies of files stored on the disk to be recovered " +
392                    "and pose a security concern.", target.Drive), LogLevel.Warning);
393            }
394           
395            //If the user is under disk quotas, log a warning message
396            if (VolumeInfo.FromMountPoint(target.Drive).HasQuota)
397                Logger.Log(S._("The drive {0} has disk quotas active. This will prevent the " +
398                    "complete erasure of unused space and may pose a security concern.",
399                    target.Drive), LogLevel.Warning);
400
401            //Get the erasure method if the user specified he wants the default.
402            ErasureMethod method = target.Method;
403
404            //Make a folder to dump our temporary files in
405            DirectoryInfo info = new DirectoryInfo(target.Drive);
406            VolumeInfo volInfo = VolumeInfo.FromMountPoint(target.Drive);
407            FileSystem fsManager = ManagerLibrary.Instance.FileSystemRegistrar[volInfo];
408
409            //Start sampling the speed of the task.
410            SteppedProgressManager progress = new SteppedProgressManager();
411            target.Progress = progress;
412            task.Progress.Steps.Add(new SteppedProgressManagerStep(
413                progress, 1.0f / task.Targets.Count));
414
415            //Erase the cluster tips of every file on the drive.
416            if (target.EraseClusterTips)
417            {
418                //Define the callback handlers
419                ProgressManager tipSearch = new ProgressManager();
420                progress.Steps.Add(new SteppedProgressManagerStep(tipSearch, 
421                    0.0f, S._("Searching for files' cluster tips...")));
422                tipSearch.Total = 1;
423                ClusterTipsSearchProgress searchProgress = delegate(string path)
424                    {
425                        if (currentTask.Canceled)
426                            throw new OperationCanceledException(S._("The task was cancelled."));
427
428                        task.OnProgressChanged(target,
429                            new ProgressChangedEventArgs(tipSearch,
430                                new TaskProgressChangedEventArgs(path, 0, 0)));
431                    };
432
433                ProgressManager tipProgress = new ProgressManager();
434                progress.Steps.Add(new SteppedProgressManagerStep(tipProgress, 0.1f,
435                    S._("Erasing cluster tips...")));
436                ClusterTipsEraseProgress eraseProgress =
437                    delegate(int currentFile, int totalFiles, string currentFilePath)
438                    {
439                        tipSearch.MarkComplete();
440                        tipProgress.Total = totalFiles;
441                        tipProgress.Completed = currentFile;
442                        task.OnProgressChanged(target,
443                            new ProgressChangedEventArgs(tipProgress,
444                                new TaskProgressChangedEventArgs(currentFilePath, 0, 0)));
445
446                        if (currentTask.Canceled)
447                            throw new OperationCanceledException(S._("The task was cancelled."));
448                    };
449
450                //Start counting statistics
451                fsManager.EraseClusterTips(VolumeInfo.FromMountPoint(target.Drive),
452                    method, searchProgress, eraseProgress);
453                tipProgress.MarkComplete();
454            }
455
456            bool lowDiskSpaceNotifications = Shell.LowDiskSpaceNotificationsEnabled;
457            info = info.CreateSubdirectory(Path.GetFileName(
458                FileSystem.GenerateRandomFileName(info, 18)));
459            try
460            {
461                //Set the folder's compression flag off since we want to use as much
462                //space as possible
463                if (info.IsCompressed())
464                    info.Uncompress();
465
466                //Disable the low disk space notifications
467                Shell.LowDiskSpaceNotificationsEnabled = false;
468
469                ProgressManager mainProgress = new ProgressManager();
470                progress.Steps.Add(new SteppedProgressManagerStep(mainProgress,
471                    target.EraseClusterTips ? 0.8f : 0.9f, S._("Erasing unused space...")));
472
473                //Continue creating files while there is free space.
474                while (volInfo.AvailableFreeSpace > 0)
475                {
476                    //Generate a non-existant file name
477                    string currFile = FileSystem.GenerateRandomFileName(info, 18);
478
479                    //Create the stream
480                    using (FileStream stream = new FileStream(currFile, FileMode.CreateNew,
481                        FileAccess.Write, FileShare.None, 8, FileOptions.WriteThrough))
482                    {
483                        //Set the length of the file to be the amount of free space left
484                        //or the maximum size of one of these dumps.
485                        mainProgress.Total = mainProgress.Completed +
486                            method.CalculateEraseDataSize(null, volInfo.AvailableFreeSpace);
487                        long streamLength = Math.Min(ErasureMethod.FreeSpaceFileUnit,
488                            mainProgress.Total);
489
490                        //Handle IO exceptions gracefully, because the filesystem
491                        //may require more space than demanded by us for file allocation.
492                        while (true)
493                            try
494                            {
495                                stream.SetLength(streamLength);
496                                break;
497                            }
498                            catch (IOException)
499                            {
500                                if (streamLength > volInfo.ClusterSize)
501                                    streamLength -= volInfo.ClusterSize;
502                                else
503                                    throw;
504                            }
505
506                        //Then run the erase task
507                        method.Erase(stream, long.MaxValue,
508                            ManagerLibrary.Instance.PrngRegistrar[ManagerLibrary.Settings.ActivePrng],
509                            delegate(long lastWritten, long totalData, int currentPass)
510                            {
511                                mainProgress.Completed += lastWritten;
512                                task.OnProgressChanged(target,
513                                    new ProgressChangedEventArgs(mainProgress,
514                                        new TaskProgressChangedEventArgs(target.Drive, currentPass, method.Passes)));
515
516                                if (currentTask.Canceled)
517                                    throw new OperationCanceledException(S._("The task was cancelled."));
518                            }
519                        );
520                    }
521                }
522
523                //Mark the main bulk of the progress as complete
524                mainProgress.MarkComplete();
525
526                //Erase old resident file system table files
527                ProgressManager residentProgress = new ProgressManager();
528                progress.Steps.Add(new SteppedProgressManagerStep(residentProgress,
529                    0.05f, S._("Old resident file system table files")));
530                fsManager.EraseOldFileSystemResidentFiles(volInfo, info, method,
531                    delegate(int currentFile, int totalFiles)
532                    {
533                        residentProgress.Completed = currentFile;
534                        residentProgress.Total = totalFiles;
535                        task.OnProgressChanged(target,
536                            new ProgressChangedEventArgs(residentProgress,
537                                new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
538
539                        if (currentTask.Canceled)
540                            throw new OperationCanceledException(S._("The task was cancelled."));
541                    }
542                );
543
544                residentProgress.MarkComplete();
545            }
546            finally
547            {
548                //Remove the folder holding all our temporary files.
549                ProgressManager tempFiles = new ProgressManager();
550                progress.Steps.Add(new SteppedProgressManagerStep(tempFiles,
551                    0.0f, S._("Removing temporary files...")));
552                task.OnProgressChanged(target, new ProgressChangedEventArgs(tempFiles,
553                    new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
554                fsManager.DeleteFolder(info);
555                tempFiles.Completed = tempFiles.Total;
556
557                //Reset the low disk space notifications
558                Shell.LowDiskSpaceNotificationsEnabled = lowDiskSpaceNotifications;
559            }
560
561            //Then clean the old file system entries
562            ProgressManager structureProgress = new ProgressManager();
563            progress.Steps.Add(new SteppedProgressManagerStep(structureProgress,
564                0.05f, S._("Erasing unused directory structures...")));
565            fsManager.EraseDirectoryStructures(volInfo,
566                delegate(int currentFile, int totalFiles)
567                {
568                    if (currentTask.Canceled)
569                        throw new OperationCanceledException(S._("The task was cancelled."));
570
571                    //Compute the progress
572                    structureProgress.Total = totalFiles;
573                    structureProgress.Completed = currentFile;
574
575                    //Set the event parameters, then broadcast the progress event.
576                    task.OnProgressChanged(target,
577                        new ProgressChangedEventArgs(structureProgress,
578                            new TaskProgressChangedEventArgs(string.Empty, 0, 0)));
579                }
580            );
581
582            structureProgress.MarkComplete();
583            target.Progress = null;
584        }
585
586        /// <summary>
587        /// Erases a file or folder on the volume.
588        /// </summary>
589        /// <param name="task">The task currently being processed.</param>
590        /// <param name="target">The target of the erasure.</param>
591        /// <param name="progress">The progress manager for the current task.</param>
592        private void EraseFilesystemObject(Task task, FileSystemObjectTarget target)
593        {
594            //Retrieve the list of files to erase.
595            long dataTotal = 0;
596            List<string> paths = target.GetPaths(out dataTotal);
597
598            //Get the erasure method if the user specified he wants the default.
599            ErasureMethod method = target.Method;
600            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
601
602            //Set the event's current target status.
603            SteppedProgressManager progress = new SteppedProgressManager();
604            target.Progress = progress;
605            task.Progress.Steps.Add(new SteppedProgressManagerStep(progress, 1.0f / task.Targets.Count));
606
607            //Iterate over every path, and erase the path.
608            for (int i = 0; i < paths.Count; ++i)
609            {
610                //Check that the file exists - we do not want to bother erasing nonexistant files
611                StreamInfo info = new StreamInfo(paths[i]);
612                if (!info.Exists)
613                {
614                    Logger.Log(S._("The file {0} was not erased as the file does not exist.",
615                        paths[i]), LogLevel.Notice);
616                    continue;
617                }
618
619                //Get the filesystem provider to handle the secure file erasures
620                FileSystem fsManager = ManagerLibrary.Instance.FileSystemRegistrar[
621                    VolumeInfo.FromMountPoint(info.DirectoryName)];
622
623                bool isReadOnly = false;
624
625                try
626                {
627                    //Update the task progress
628                    ProgressManager step = new ProgressManager();
629                    progress.Steps.Add(new SteppedProgressManagerStep(step,
630                        info.Length / (float)dataTotal, S._("Erasing files...")));
631                    task.OnProgressChanged(target,
632                        new ProgressChangedEventArgs(step,
633                            new TaskProgressChangedEventArgs(paths[i], 0, method.Passes)));
634
635                    //Remove the read-only flag, if it is set.
636                    if (isReadOnly = info.IsReadOnly)
637                        info.IsReadOnly = false;
638
639                    //Make sure the file does not have any attributes which may affect
640                    //the erasure process
641                    if ((info.Attributes & FileAttributes.Compressed) != 0 ||
642                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
643                        (info.Attributes & FileAttributes.SparseFile) != 0)
644                    {
645                        //Log the error
646                        Logger.Log(S._("The file {0} could not be erased because the file was " +
647                            "either compressed, encrypted or a sparse file.", info.FullName),
648                            LogLevel.Error);
649                        continue;
650                    }
651
652                    fsManager.EraseFileSystemObject(info, method,
653                        delegate(long lastWritten, long totalData, int currentPass)
654                        {
655                            if (currentTask.Canceled)
656                                throw new OperationCanceledException(S._("The task was cancelled."));
657
658                            step.Total = totalData;
659                            step.Completed += lastWritten;
660                            task.OnProgressChanged(target,
661                                new ProgressChangedEventArgs(step,
662                                    new TaskProgressChangedEventArgs(info.FullName, currentPass, method.Passes)));
663                        });
664
665                    //Remove the file.
666                    FileInfo fileInfo = info.File;
667                    if (fileInfo != null)
668                        fsManager.DeleteFile(fileInfo);
669                    step.MarkComplete();
670                }
671                catch (UnauthorizedAccessException)
672                {
673                    Logger.Log(S._("The file {0} could not be erased because the file's " +
674                        "permissions prevent access to the file.", info.FullName), LogLevel.Error);
675                }
676                catch (IOException)
677                {
678                    if (System.Runtime.InteropServices.Marshal.GetLastWin32Error() ==
679                        Win32ErrorCode.SharingViolation)
680                    {
681                        if (!ManagerLibrary.Settings.ForceUnlockLockedFiles)
682                            throw;
683
684                        List<System.Diagnostics.Process> processes =
685                            new List<System.Diagnostics.Process>();
686                        foreach (OpenHandle handle in OpenHandle.Items)
687                            if (handle.Path == paths[i])
688                                processes.Add(System.Diagnostics.Process.GetProcessById(handle.ProcessId));
689
690                        string lockedBy = null;
691                        if (processes.Count > 0)
692                        {
693                            StringBuilder processStr = new StringBuilder();
694                            foreach (System.Diagnostics.Process process in processes)
695                            {
696                                try
697                                {
698                                    processStr.AppendFormat(System.Globalization.CultureInfo.InvariantCulture,
699                                        "{0}, ", process.MainModule.FileName);
700                                }
701                                catch (System.ComponentModel.Win32Exception)
702                                {
703                                }
704                            }
705
706                            lockedBy = S._("(locked by {0})", processStr.ToString().Remove(processStr.Length - 2));
707                        }
708
709                        Logger.Log(S._("Could not force closure of file \"{0}\" {1}", paths[i],
710                            lockedBy == null ? string.Empty : lockedBy).Trim(), LogLevel.Error);
711                    }
712                    else
713                        throw;
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.