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

Revision 1196, 30.7 KB checked in by lowjoel, 5 years ago (diff)

Fixed FxCop? warning 1034 and 1725, don't nest public types and overrides should match base declaration.

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