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

Revision 2331, 32.3 KB checked in by lowjoel, 3 years ago (diff)

Merged revision(s) 2330 from trunk/eraser: Reschedule the task for execution, in addition to just computing the next run time. Fixes #387.

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