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

Revision 2168, 32.2 KB checked in by lowjoel, 5 years ago (diff)

Fix crash when a volume is connected but not mounted.

  • 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                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
323
324                        //If the task is an execute on restart task, it is only run
325                        //once and can now be restored to an immediately executed task
326                        if (task.Schedule == Schedule.RunOnRestart)
327                            task.Schedule = Schedule.RunNow;
328
329                        //And the task finished event.
330                        task.OnTaskFinished(new TaskEventArgs(task));
331                        OnTaskProcessed(new TaskEventArgs(task));
332
333                        //Remove the actively executing task from our instance variable
334                        currentTask = null;
335                    }
336                }
337
338                //Wait for half a minute to check for the next scheduled task.
339                schedulerInterrupt.WaitOne(30000, false);
340            }
341        }
342
343        /// <summary>
344        /// Manages the progress for any operation.
345        /// </summary>
346        private class ProgressManager
347        {
348            /// <summary>
349            /// Starts measuring the speed of the task.
350            /// </summary>
351            public void Start()
352            {
353                startTime = DateTime.Now;
354            }
355
356            /// <summary>
357            /// Tracks the amount of the operation completed.
358            /// </summary>
359            public long Completed
360            {
361                get
362                {
363                    return completed;
364                }
365                set
366                {
367                    lastCompleted += value - completed;
368                    completed = value;
369                }
370            }
371
372            /// <summary>
373            /// The amount to reach before the operation completes.
374            /// </summary>
375            public long Total
376            {
377                get
378                {
379                    return total;
380                }
381                set
382                {
383                    total = value;
384                }
385            }
386
387            /// <summary>
388            /// Gets the percentage of the operation completed.
389            /// </summary>
390            public float Progress
391            {
392                get
393                {
394                    return (float)((double)Completed / Total);
395                }
396            }
397
398            /// <summary>
399            /// Computes the speed of the erase, in units of completion per second,
400            /// based on the information collected in the previous 15 seconds.
401            /// </summary>
402            public int Speed
403            {
404                get
405                {
406                    if (DateTime.Now == startTime)
407                        return 0;
408
409                    if ((DateTime.Now - lastSpeedCalc).Seconds < 5 && lastSpeed != 0)
410                        return lastSpeed;
411
412                    //Calculate how much time has passed
413                    double timeElapsed = (DateTime.Now - lastSpeedCalc).TotalSeconds;
414                    if (timeElapsed == 0.0)
415                        return 0;
416
417                    //Then compute the speed of the calculation
418                    lastSpeed = (int)(lastCompleted / timeElapsed);
419                    lastSpeedCalc = DateTime.Now;
420                    lastCompleted = 0;
421                    return lastSpeed;
422                }
423            }
424
425            /// <summary>
426            /// Calculates the estimated amount of time left based on the total
427            /// amount of information to erase and the current speed of the erase
428            /// </summary>
429            public TimeSpan TimeLeft
430            {
431                get
432                {
433                    if (Speed == 0)
434                        return new TimeSpan(0, 0, -1);
435                    return new TimeSpan(0, 0, (int)((Total - Completed) / Speed));
436                }
437            }
438
439            /// <summary>
440            /// The starting time of the operation, used to determine average speed.
441            /// </summary>
442            private DateTime startTime;
443
444            /// <summary>
445            /// The last time a speed calculation was computed so that speed is not
446            /// computed too often.
447            /// </summary>
448            private DateTime lastSpeedCalc;
449
450            /// <summary>
451            /// The last calculated speed of the operation.
452            /// </summary>
453            private int lastSpeed;
454
455            /// <summary>
456            /// The amount of the operation completed since the last speed computation.
457            /// </summary>
458            private long lastCompleted;
459
460            /// <summary>
461            /// The amount of the operation completed.
462            /// </summary>
463            private long completed;
464
465            /// <summary>
466            /// The amount to reach before the operation is completed.
467            /// </summary>
468            private long total;
469        }
470
471        /// <summary>
472        /// Provides a common interface to track the progress made by the Erase functions.
473        /// </summary>
474        private class TaskProgressManager : ProgressManager
475        {
476            /// <summary>
477            /// Constructor.
478            /// </summary>
479            public TaskProgressManager(Task task)
480            {
481                foreach (ErasureTarget target in task.Targets)
482                    Total += target.TotalData;
483
484                Event = new TaskProgressEventArgs(task);
485                Start();
486            }
487
488            /// <summary>
489            /// The TaskProgressEventArgs object representing the progress of the current
490            /// task.
491            /// </summary>
492            public TaskProgressEventArgs Event
493            {
494                get
495                {
496                    return evt;
497                }
498                set
499                {
500                    evt = value;
501                }
502            }
503
504            private TaskProgressEventArgs evt;
505        }
506
507        /// <summary>
508        /// Executes a unused space erase.
509        /// </summary>
510        /// <param name="task">The task currently being executed</param>
511        /// <param name="target">The target of the unused space erase.</param>
512        /// <param name="progress">The progress manager object managing the progress of the task</param>
513        private void EraseUnusedSpace(Task task, UnusedSpaceTarget target, TaskProgressManager progress)
514        {
515            //Check for sufficient privileges to run the unused space erasure.
516            if (!AdvApi.IsAdministrator())
517            {
518                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
519                    Environment.OSVersion.Version >= new Version(6, 0))
520                {
521                    throw new UnauthorizedAccessException(S._("The program does not have the " +
522                        "required permissions to erase the unused space on disk. Run the program " +
523                        "as an administrator and retry the operation."));
524                }
525                else
526                    throw new UnauthorizedAccessException(S._("The program does not have the " +
527                        "required permissions to erase the unused space on disk"));
528            }
529
530            //If the user is under disk quotas, log a warning message
531            if (VolumeInfo.FromMountpoint(target.Drive).HasQuota)
532                task.Log.LastSessionEntries.Add(new LogEntry(S._("The drive which is having its " +
533                    "unused space erased has disk quotas active. This will prevent the complete " +
534                    "erasure of unused space and will pose a security concern"), LogLevel.Warning));
535
536            //Get the erasure method if the user specified he wants the default.
537            ErasureMethod method = target.Method;
538
539            //Make a folder to dump our temporary files in
540            DirectoryInfo info = new DirectoryInfo(target.Drive);
541            VolumeInfo volInfo = VolumeInfo.FromMountpoint(target.Drive);
542            FileSystem fsManager = FileSystemManager.Get(volInfo);
543           
544            //Erase the cluster tips of every file on the drive.
545            if (target.EraseClusterTips)
546            {
547                progress.Event.CurrentTargetStatus = S._("Searching for files' cluster tips...");
548                progress.Event.CurrentTargetTotalPasses = method.Passes;
549                progress.Event.CurrentItemProgress = -1.0f;
550                progress.Event.TimeLeft = new TimeSpan(0, 0, -1);
551
552                //Start counting statistics
553                ProgressManager tipProgress = new ProgressManager();
554                tipProgress.Start();
555
556                //Define the callback handlers
557                ClusterTipsSearchProgress searchProgress = delegate(string path)
558                    {
559                        progress.Event.CurrentItemName = path;
560                        task.OnProgressChanged(progress.Event);
561
562                        if (currentTask.Canceled)
563                            throw new OperationCanceledException(S._("The task was cancelled."));
564                    };
565
566                ClusterTipsEraseProgress eraseProgress =
567                    delegate(int currentFile, int totalFiles, string currentFilePath)
568                    {
569                        tipProgress.Total = totalFiles;
570                        tipProgress.Completed = currentFile;
571
572                        progress.Event.CurrentTargetStatus = S._("Erasing cluster tips...");
573                        progress.Event.CurrentItemName = currentFilePath;
574                        progress.Event.CurrentItemProgress = tipProgress.Progress;
575                        progress.Event.CurrentTargetProgress = progress.Event.CurrentItemProgress / 10;
576                        progress.Event.TimeLeft = tipProgress.TimeLeft;
577                        task.OnProgressChanged(progress.Event);
578
579                        if (currentTask.Canceled)
580                            throw new OperationCanceledException(S._("The task was cancelled."));
581                    };
582
583                fsManager.EraseClusterTips(VolumeInfo.FromMountpoint(target.Drive),
584                    method, task.Log, searchProgress, eraseProgress);
585            }
586
587            info = info.CreateSubdirectory(Path.GetFileName(
588                FileSystem.GenerateRandomFileName(info, 18)));
589            try
590            {
591                //Set the folder's compression flag off since we want to use as much
592                //space as possible
593                if (Eraser.Util.File.IsCompressed(info.FullName))
594                    Eraser.Util.File.SetCompression(info.FullName, false);
595
596                //Continue creating files while there is free space.
597                progress.Event.CurrentTargetStatus = S._("Erasing unused space...");
598                progress.Event.CurrentItemName = target.Drive;
599                task.OnProgressChanged(progress.Event);
600                while (volInfo.AvailableFreeSpace > 0)
601                {
602                    //Generate a non-existant file name
603                    string currFile = FileSystem.GenerateRandomFileName(info, 18);
604
605                    //Create the stream
606                    using (FileStream stream = new FileStream(currFile, FileMode.CreateNew,
607                        FileAccess.Write, FileShare.None, 8, FileOptions.WriteThrough))
608                    {
609                        //Set the length of the file to be the amount of free space left
610                        //or the maximum size of one of these dumps.
611                        long streamLength = Math.Min(ErasureMethod.FreeSpaceFileUnit,
612                            volInfo.AvailableFreeSpace);
613
614                        //Handle IO exceptions gracefully, because the filesystem
615                        //may require more space than demanded by us for file allocation.
616                        while (true)
617                            try
618                            {
619                                stream.SetLength(streamLength);
620                                break;
621                            }
622                            catch (IOException)
623                            {
624                                if (streamLength > volInfo.ClusterSize)
625                                    streamLength -= volInfo.ClusterSize;
626                                else
627                                    throw;
628                            }
629
630                        //Then run the erase task
631                        method.Erase(stream, long.MaxValue,
632                            PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng),
633                            delegate(long lastWritten, long totalData, int currentPass)
634                            {
635                                progress.Completed = Math.Min(progress.Total,
636                                    progress.Completed + lastWritten);
637                                progress.Event.CurrentItemPass = currentPass;
638                                progress.Event.CurrentItemProgress = progress.Progress;
639                                if (target.EraseClusterTips)
640                                    progress.Event.CurrentTargetProgress = (float)
641                                        (0.1f + progress.Event.CurrentItemProgress * 0.8f);
642                                else
643                                    progress.Event.CurrentTargetProgress = (float)
644                                        (progress.Event.CurrentItemProgress * 0.9f);
645                                progress.Event.TimeLeft = progress.TimeLeft;
646                                task.OnProgressChanged(progress.Event);
647
648                                if (currentTask.Canceled)
649                                    throw new OperationCanceledException(S._("The task was cancelled."));
650                            }
651                        );
652                    }
653                }
654
655                //Erase old resident file system table files
656                progress.Event.CurrentItemName = S._("Old resident file system table files");
657                task.OnProgressChanged(progress.Event);
658                ProgressManager residentFilesProgress = new ProgressManager();
659                residentFilesProgress.Start();
660                fsManager.EraseOldFileSystemResidentFiles(volInfo, info, method,
661                    delegate(int currentFile, int totalFiles)
662                    {
663                        residentFilesProgress.Completed = currentFile;
664                        residentFilesProgress.Total = totalFiles;
665                        progress.Event.CurrentItemProgress = residentFilesProgress.Progress;
666                        progress.Event.TimeLeft = residentFilesProgress.TimeLeft;
667                        task.OnProgressChanged(progress.Event);
668
669                        if (currentTask.Canceled)
670                            throw new OperationCanceledException(S._("The task was cancelled."));
671                    }
672                );
673            }
674            finally
675            {
676                //Remove the folder holding all our temporary files.
677                progress.Event.CurrentTargetStatus = S._("Removing temporary files...");
678                task.OnProgressChanged(progress.Event);
679                fsManager.DeleteFolder(info);
680            }
681
682            //Then clean the old file system entries
683            progress.Event.CurrentTargetStatus = S._("Erasing unused directory structures...");
684            ProgressManager fsEntriesProgress = new ProgressManager();
685            fsEntriesProgress.Start();
686            fsManager.EraseDirectoryStructures(volInfo,
687                delegate(int currentFile, int totalFiles)
688                {
689                    if (currentTask.Canceled)
690                        throw new OperationCanceledException(S._("The task was cancelled."));
691
692                    //Compute the progress
693                    fsEntriesProgress.Total = totalFiles;
694                    fsEntriesProgress.Completed = currentFile;
695
696                    //Set the event parameters, then broadcast the progress event.
697                    progress.Event.TimeLeft = fsEntriesProgress.TimeLeft;
698                    progress.Event.CurrentItemProgress = fsEntriesProgress.Progress;
699                    progress.Event.CurrentTargetProgress = (float)(
700                        0.9 + progress.Event.CurrentItemProgress / 10);
701                    task.OnProgressChanged(progress.Event);
702                }
703            );
704        }
705
706        /// <summary>
707        /// Traverses the given folder and deletes it securely only if it is
708        /// empty.
709        /// </summary>
710        /// <param name="info">The folder to check.</param>
711        private delegate void FolderEraseDelegate(DirectoryInfo info);
712
713        /// <summary>
714        /// Erases a file or folder on the volume.
715        /// </summary>
716        /// <param name="task">The task currently being processed.</param>
717        /// <param name="target">The target of the erasure.</param>
718        /// <param name="progress">The progress manager for the current task.</param>
719        private void EraseFilesystemObject(Task task, FileSystemObjectTarget target,
720            TaskProgressManager progress)
721        {
722            //Retrieve the list of files to erase.
723            long dataTotal = 0;
724            List<string> paths = target.GetPaths(out dataTotal);
725
726            //Get the erasure method if the user specified he wants the default.
727            ErasureMethod method = target.Method;
728
729            //Calculate the total amount of data required to finish the wipe.
730            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
731
732            //Set the event's current target status.
733            progress.Event.CurrentTargetStatus = S._("Erasing files...");
734
735            //Iterate over every path, and erase the path.
736            for (int i = 0; i < paths.Count; ++i)
737            {
738                //Update the task progress
739                progress.Event.CurrentTargetProgress = i / (float)paths.Count;
740                progress.Event.CurrentTarget = target;
741                progress.Event.CurrentItemName = paths[i];
742                progress.Event.CurrentItemProgress = 0;
743                progress.Event.CurrentTargetTotalPasses = method.Passes;
744                task.OnProgressChanged(progress.Event);
745               
746                //Check that the file exists - we do not want to bother erasing nonexistant files
747                StreamInfo info = new StreamInfo(paths[i]);
748                if (!info.Exists)
749                {
750                    task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} was not erased " +
751                        "as the file does not exist.", paths[i]), LogLevel.Notice));
752                    continue;
753                }
754
755                //Get the filesystem provider to handle the secure file erasures
756                FileSystem fsManager = FileSystemManager.Get(
757                    VolumeInfo.FromMountpoint(info.DirectoryName));
758
759                bool isReadOnly = false;
760               
761                try
762                {
763                    //Remove the read-only flag, if it is set.
764                    if (isReadOnly = info.IsReadOnly)
765                        info.IsReadOnly = false;
766
767                    //Make sure the file does not have any attributes which may affect
768                    //the erasure process
769                    if ((info.Attributes & FileAttributes.Compressed) != 0 || 
770                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
771                        (info.Attributes & FileAttributes.SparseFile) != 0)
772                    {
773                        //Log the error
774                        task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could " +
775                            "not be erased because the file was either compressed, encrypted or " +
776                            "a sparse file.", info.FullName), LogLevel.Error));
777                        continue;
778                    }
779
780                    long itemWritten = 0;
781                    fsManager.EraseFileSystemObject(info, method,
782                        delegate(long lastWritten, long totalData, int currentPass)
783                        {
784                            dataTotal -= lastWritten;
785                            progress.Completed += lastWritten;
786                            progress.Event.CurrentItemPass = currentPass;
787                            progress.Event.CurrentItemProgress = (float)
788                                ((itemWritten += lastWritten) / (float)totalData);
789                            progress.Event.CurrentTargetProgress =
790                                (i + progress.Event.CurrentItemProgress) /
791                                (float)paths.Count;
792                            progress.Event.TimeLeft = progress.TimeLeft;
793                            task.OnProgressChanged(progress.Event);
794
795                            if (currentTask.Canceled)
796                                throw new OperationCanceledException(S._("The task was cancelled."));
797                        });
798
799                    //Remove the file.
800                    FileInfo fileInfo = info.File;
801                    if (fileInfo != null)
802                        fsManager.DeleteFile(fileInfo);
803                }
804                catch (UnauthorizedAccessException)
805                {
806                    task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could not " +
807                        "be erased because the file's permissions prevent access to the file.",
808                        info.FullName), LogLevel.Error));
809                }
810                catch (FileLoadException)
811                {
812                    if (!ManagerLibrary.Settings.ForceUnlockLockedFiles)
813                        throw;
814
815                    List<System.Diagnostics.Process> processes = new List<System.Diagnostics.Process>();
816                    foreach (OpenHandle handle in OpenHandle.Items)
817                        if (handle.Path == paths[i])
818                            processes.Add(System.Diagnostics.Process.GetProcessById(handle.ProcessId));
819
820                    string lockedBy = null;
821                    if (processes.Count > 0)
822                    {
823                        StringBuilder processStr = new StringBuilder();
824                        foreach (System.Diagnostics.Process process in processes)
825                        {
826                            try
827                            {
828                                processStr.AppendFormat(System.Globalization.CultureInfo.InvariantCulture,
829                                    "{0}, ", process.MainModule.FileName);
830                            }
831                            catch (System.ComponentModel.Win32Exception)
832                            {
833                            }
834                        }
835
836                        lockedBy = S._("(locked by {0})", processStr.ToString().Remove(processStr.Length - 2));
837                    }
838
839                    task.Log.LastSessionEntries.Add(new LogEntry(S._(
840                        "Could not force closure of file \"{0}\" {1}", paths[i],
841                        lockedBy == null ? string.Empty : lockedBy).Trim(), LogLevel.Error));
842                }
843                finally
844                {
845                    //Re-set the read-only flag if the file exists (i.e. there was an error)
846                    if (isReadOnly && info.Exists && !info.IsReadOnly)
847                        info.IsReadOnly = isReadOnly;
848                }
849            }
850
851            //If the user requested a folder removal, do it.
852            if (target is FolderTarget)
853            {
854                progress.Event.CurrentTargetStatus = S._("Removing folders...");
855               
856                //Remove all subfolders which are empty.
857                FolderTarget fldr = (FolderTarget)target;
858                FileSystem fsManager = FileSystemManager.Get(VolumeInfo.FromMountpoint(fldr.Path));
859                FolderEraseDelegate eraseEmptySubFolders = null;
860                eraseEmptySubFolders = delegate(DirectoryInfo info)
861                {
862                    foreach (DirectoryInfo subDir in info.GetDirectories())
863                        eraseEmptySubFolders(subDir);
864
865                    progress.Event.CurrentItemName = info.FullName;
866                    task.OnProgressChanged(progress.Event);
867
868                    FileSystemInfo[] files = info.GetFileSystemInfos();
869                    if (files.Length == 0)
870                        try
871                        {
872                            fsManager.DeleteFolder(info);
873                        }
874                        catch (UnauthorizedAccessException)
875                        {
876                            task.Log.LastSessionEntries.Add(new LogEntry(S._(
877                                "The folder {0} could not be deleted because the folder's " +
878                                "permissions prevents the deletion of the folder.", info.FullName),
879                                LogLevel.Error));
880                        }
881                };
882
883                DirectoryInfo directory = new DirectoryInfo(fldr.Path);
884                foreach (DirectoryInfo subDir in directory.GetDirectories())
885                    eraseEmptySubFolders(subDir);
886
887                if (fldr.DeleteIfEmpty)
888                {
889                    progress.Event.CurrentItemName = directory.FullName;
890                    task.OnProgressChanged(progress.Event);
891
892                    //See if this is the root of a volume.
893                    bool isVolumeRoot = directory.Parent == null;
894                    foreach (VolumeInfo volume in VolumeInfo.Volumes)
895                        if (volume.IsReady)
896                            foreach (string mountPoint in volume.MountPoints)
897                                if (directory.FullName == mountPoint)
898                                    isVolumeRoot = true;
899
900                    //If the folder is a mount point, then don't delete it. If it isn't,
901                    //search for files under the folder to see if it is empty.
902                    if (!isVolumeRoot && directory.Exists &&
903                        directory.GetFiles("*", SearchOption.AllDirectories).Length == 0)
904                    {
905                        fsManager.DeleteFolder(directory);
906                    }
907                }
908            }
909
910            //If the user was erasing the recycle bin, clear the bin.
911            if (target is RecycleBinTarget)
912            {
913                progress.Event.CurrentTargetStatus = S._("Emptying recycle bin...");
914                task.OnProgressChanged(progress.Event);
915
916                ShellApi.EmptyRecycleBin(EmptyRecycleBinOptions.NoConfirmation |
917                    EmptyRecycleBinOptions.NoProgressUI | EmptyRecycleBinOptions.NoSound);
918            }
919        }
920
921        /// <summary>
922        /// The thread object.
923        /// </summary>
924        private Thread thread;
925
926        /// <summary>
927        /// The lock preventing concurrent access for the tasks list and the
928        /// tasks queue.
929        /// </summary>
930        private object tasksLock = new object();
931
932        /// <summary>
933        /// The queue of tasks. This queue is executed when the first element's
934        /// timestamp (the key) has been past. This list assumes that all tasks
935        /// are sorted by timestamp, smallest one first.
936        /// </summary>
937        private SortedList<DateTime, List<Task>> scheduledTasks =
938            new SortedList<DateTime, List<Task>>();
939
940        /// <summary>
941        /// The task list associated with this executor instance.
942        /// </summary>
943        private DirectExecutorTasksCollection tasks;
944
945        /// <summary>
946        /// The currently executing task.
947        /// </summary>
948        Task currentTask;
949
950        /// <summary>
951        /// An automatically reset event allowing the addition of new tasks to
952        /// interrupt the thread's sleeping state waiting for the next recurring
953        /// task to be due.
954        /// </summary>
955        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
956
957        private class DirectExecutorTasksCollection : ExecutorTasksCollection
958        {
959            /// <summary>
960            /// Constructor.
961            /// </summary>
962            /// <param name="executor">The <see cref="DirectExecutor"/> object owning
963            /// this list.</param>
964            public DirectExecutorTasksCollection(DirectExecutor executor)
965                : base(executor)
966            {
967            }
968
969            #region IList<Task> Members
970            public override int IndexOf(Task item)
971            {
972                return list.IndexOf(item);
973            }
974
975            public override void Insert(int index, Task item)
976            {
977                item.Executor = Owner;
978                lock (list)
979                    list.Insert(index, item);
980
981                //If the task is scheduled to run now, break the waiting thread and
982                //run it immediately
983                if (item.Schedule == Schedule.RunNow)
984                {
985                    Owner.QueueTask(item);
986                }
987                //If the task is scheduled, add the next execution time to the list
988                //of schduled tasks.
989                else if (item.Schedule != Schedule.RunOnRestart)
990                {
991                    Owner.ScheduleTask(item);
992                }
993
994                //Call all the event handlers who registered to be notified of tasks
995                //being added.
996                Owner.OnTaskAdded(new TaskEventArgs(item));
997            }
998
999            public override void RemoveAt(int index)
1000            {
1001                lock (list)
1002                {
1003                    Task task = list[index];
1004                    task.Cancel();
1005                    task.Executor = null;
1006                    list.RemoveAt(index);
1007
1008                    //Call all event handlers registered to be notified of task deletions.
1009                    Owner.OnTaskDeleted(new TaskEventArgs(task));
1010                }
1011            }
1012
1013            public override Task this[int index]
1014            {
1015                get
1016                {
1017                    lock (list)
1018                        return list[index];
1019                }
1020                set
1021                {
1022                    lock (list)
1023                        list[index] = value;
1024                }
1025            }
1026            #endregion
1027
1028            #region ICollection<Task> Members
1029            public override void Add(Task item)
1030            {
1031                Insert(Count, item);
1032            }
1033
1034            public override void Clear()
1035            {
1036                foreach (Task task in list)
1037                    Remove(task);
1038            }
1039
1040            public override bool Contains(Task item)
1041            {
1042                lock (list)
1043                    return list.Contains(item);
1044            }
1045
1046            public override void CopyTo(Task[] array, int arrayIndex)
1047            {
1048                lock (list)
1049                    list.CopyTo(array, arrayIndex);
1050            }
1051
1052            public override int Count
1053            {
1054                get
1055                {
1056                    lock (list)
1057                        return list.Count;
1058                }
1059            }
1060
1061            public override bool Remove(Task item)
1062            {
1063                lock (list)
1064                {
1065                    int index = list.IndexOf(item);
1066                    if (index < 0)
1067                        return false;
1068
1069                    RemoveAt(index);
1070                }
1071
1072                return true;
1073            }
1074            #endregion
1075
1076            #region IEnumerable<Task> Members
1077            public override IEnumerator<Task> GetEnumerator()
1078            {
1079                return list.GetEnumerator();
1080            }
1081            #endregion
1082
1083            public override void SaveToStream(Stream stream)
1084            {
1085                lock (list)
1086                    new BinaryFormatter().Serialize(stream, list);
1087            }
1088
1089            public override void LoadFromStream(Stream stream)
1090            {
1091                //Load the list into the dictionary
1092                StreamingContext context = new StreamingContext(
1093                    StreamingContextStates.All, Owner);
1094                BinaryFormatter formatter = new BinaryFormatter(null, context);
1095                List<Task> deserialised = (List<Task>)formatter.Deserialize(stream);
1096                list.AddRange(deserialised);
1097
1098                foreach (Task task in deserialised)
1099                {
1100                    Owner.OnTaskAdded(new TaskEventArgs(task));
1101                    if (task.Schedule is RecurringSchedule)
1102                        Owner.ScheduleTask(task);
1103                }
1104            }
1105
1106            /// <summary>
1107            /// The data store for this object.
1108            /// </summary>
1109            private List<Task> list = new List<Task>();
1110        }
1111    }
1112}
Note: See TracBrowser for help on using the repository browser.