source: branches/eraser6/6.0/Eraser.Manager/DirectExecutor.cs @ 1643

Revision 1643, 31.1 KB checked in by lowjoel, 4 years ago (diff)

Fixed crash when we try to close open handles and where no handles were found to match the file in use, leading to us doing a substring on a string which is blank.

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