1
\$\begingroup\$

I am writing an application that either mirrors a file/directory into another location or creates a zip file "snap shot" of a file or directory and stores it in another location. The user is able to create "rules" where they define the source file/directory, the destination file/directory and if they are archiving how many archives to keep. The user is also able to exclude files/directories if they so choose. Now this service class is a lot of code and I am wondering if there is a better way to do this. Of course all of the methods that can be called by the interface are called with a BackgroundWorker.RunAsync() method. Any feed back on how to make this more efficient would be super appreciated.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Net.Mime;
using System.Windows.Forms;
using Backupr.Helpers;
using Backupr.Models;

namespace Backupr.Services
{
    public interface IFileActionService
    {
        void ArchiveRule(BackupRule rule);
        void EndOfDay();
    }

    public class FileActionService : IFileActionService
    {     
        public void ArchiveRule(BackupRule rule)
        {
            ArchiveAction archiver = new ArchiveAction(rule);
            archiver.MoveFilesToTemp();
            if (rule.Type == RuleType.Archive)
            {
                archiver.ZipTempFile();
                archiver.MoveArchiveToDestinations();
            }
            else if (rule.Type == RuleType.Mirror)
            {
                archiver.MirrorRule();
            }
            archiver.CleanTempDirectory();
            archiver.CleanOldBackups();
        }

        public void EndOfDay()
        {
            IRuleService ruleService = new RuleService();
            MessageBox.Show("Warning this action will reboot your PC!");
            foreach (BackupRule rule in ruleService.GetAll().Where(r => r.EndOfDay == true))
            {
                ArchiveRule(rule);
            }
            Process.Start("shutdown.exe", "/r /f /t 1");
        }
    }

    // HACK: need to find a better way to handle deleted files and directories.
    // perhaps a logging system that lets the user know the file is missing
    // or just removing the deleted file from the rule.
    // refer to MoveFilesToTemp(), MirrorRule(), DirectoryCopy()
    internal class ArchiveAction
    {
        // TODO: allow users to define archiveName via built in tokens.
        private string archiveName = System.Environment.MachineName + '_' + DateTime.Now.ToString("yyyyMMddhhmmss");
        private BackupRule rule;
        private List<string> excludes = new List<string>();

        public ArchiveAction(BackupRule _rule)
        {
            rule = _rule;
            foreach (BackupPath excludePath in rule.Paths.Where(p => p.Type == PathType.Exclude))
            {
                excludes.Add(excludePath.Value);
            }
        }

        public void MoveFilesToTemp()
        {         
            foreach (BackupPath sourcePath in rule.Paths.Where(p => p.Type == PathType.Source))
            {
                // See comment at top of ArchiveAction Class
                if (PathIsDirectory(sourcePath.Value))
                {
                    DirectoryCopy(sourcePath.Value, Path.Combine(tempDirectory, Path.GetFileName(sourcePath.Value)), true);
                }
                else if (File.Exists(sourcePath.Value) && !excludes.Any(p => p == sourcePath.Value))
                {
                    File.Copy(sourcePath.Value, Path.Combine(tempDirectory, Path.GetFileName(sourcePath.Value)));
                }
            }
        }

        public void ZipTempFile()
        {
            using (Package zip = Package.Open(Path.Combine(Asset.BackuprTempDirectory, archiveName + ".zip"), FileMode.Create))
            {
                foreach (string file in Directory.GetFiles(tempDirectory, "*.*", SearchOption.AllDirectories))
                {
                    string packageFile = file.Replace(' ', '_');
                    Uri uri = new Uri(packageFile.Substring(tempDirectory.Length), UriKind.Relative);
                    PackagePart ZipPart = zip.CreatePart(PackUriHelper.CreatePartUri(uri), MediaTypeNames.Application.Zip, CompressionOption.Maximum);
                    byte[] B = File.ReadAllBytes(file);
                    ZipPart.GetStream().Write(B, 0, B.Length);
                }
            }
        }

        public void MoveArchiveToDestinations()
        {
            foreach (BackupPath destinationPath in rule.Paths.Where(p => p.Type == PathType.Destination))
            {
                if (!Directory.Exists(destinationPath.Value))
                {
                    Directory.CreateDirectory(destinationPath.Value);
                }
                File.Copy(Path.Combine(Asset.BackuprTempDirectory, archiveName + ".zip"), Path.Combine(destinationPath.Value, archiveName + ".zip"));
            }
        }

        public void MirrorRule()
        {
            foreach (BackupPath destinationPath in rule.Paths.Where(p => p.Type == PathType.Destination))
            {
                DirectoryCopy(tempDirectory, destinationPath.Value, true);
            }
        }

        public void CleanTempDirectory()
        {
            Directory.Delete(Asset.BackuprTempDirectory, true);
        }

        public void CleanOldBackups()
        {
            if (rule.Type == RuleType.Archive)
            {
                foreach (BackupPath destinationPath in rule.Paths.Where(p => p.Type == PathType.Destination))
                {
                    DirectoryInfo directory = new DirectoryInfo(destinationPath.Value);
                    if (!directory.Exists)
                    {
                        // See comment at top of ArchiveAction Class
                        break;
                    }
                    FileInfo[] files = directory.GetFiles();
                    foreach (FileInfo file in files.OrderByDescending(p => p.LastWriteTime).Skip(rule.BackupsToKeep))
                    {
                        string filePath = Path.Combine(destinationPath.Value, file.Name);
                        File.Delete(filePath);
                    }
                }
            }
        }

        private bool PathIsDirectory(string path)
        {
            FileAttributes fileAttribute = File.GetAttributes(@path);
            if ((fileAttribute & FileAttributes.Directory) == FileAttributes.Directory)
            {
                return true;
            }
            return false;
        }

        private string tempDirectory
        {
            get
            {
                string directory = Path.Combine(Asset.BackuprTempDirectory, archiveName);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                return directory;
            }
        }

        private void DirectoryCopy(string source, string destination, bool copySubDirectories)
        {
            if (excludes.Any(p => p == source))
            {
                return;
            }

            DirectoryInfo directory = new DirectoryInfo(source);
            if (!directory.Exists)
            {
                // See comment at top of ArchiveAction Class
                return;
            }
            if (!Directory.Exists(destination))
            {
                Directory.CreateDirectory(destination);
            }
            FileInfo[] files = directory.GetFiles();
            foreach (FileInfo file in files)
            {
                if (!excludes.Any(p => p == file.FullName))
                {
                    string temppath = Path.Combine(destination, file.Name);
                    file.CopyTo(temppath, true);
                }
            }
            if (copySubDirectories)
            {
                DirectoryInfo[] subDirectories = directory.GetDirectories();
                foreach (DirectoryInfo subDirectory in subDirectories)
                {
                    if (!excludes.Any(p => p == subDirectory.FullName))
                    {
                        string temppath = Path.Combine(destination, subDirectory.Name);
                        DirectoryCopy(subDirectory.FullName, temppath, copySubDirectories);
                    }
                }
            }
        }
    }
}

SIDE NOTE: Everthing is moved to a temp directory first because I was having trouble with the zip method and the DirectoryCopy method not copying a directory directly in the source path root.

Example: assuming "C:\Test" is the source and "D:\Backup" is the destination and "C:\Test\directory" has a file in it called "test.txt" the output would look like "D:\Backup\test.txt" instead of "D:\Backup\directory\test.txt", however any directories inside of "C:\Test\directory" would be copied over correctly.

\$\endgroup\$
  • \$\begingroup\$ I failed to mention I am using System.IO.Packaging instead of System.IO.Compression because this is being developed on .NET 4.0 instead of .NET 4.5 \$\endgroup\$ – aaronmallen Jul 23 '14 at 17:46
  • \$\begingroup\$ are you still up to solve this problem? there are lots of problem , but I need to see some more code as present code did not compile \$\endgroup\$ – paritosh Jul 26 '14 at 14:45
  • \$\begingroup\$ That's all of it \$\endgroup\$ – aaronmallen Jul 26 '14 at 16:10
  • \$\begingroup\$ I could not find Backupr.Helpers and Backupr.Models; in code \$\endgroup\$ – paritosh Jul 26 '14 at 16:14
  • \$\begingroup\$ Those are the name spaces that contain my entity classes and the Asset class which defines backuprTempDirectory. The name of the app is backupr. \$\endgroup\$ – aaronmallen Jul 26 '14 at 16:19
1
\$\begingroup\$

As rule has been passed to ArchiveAction class , you dont need to check type inside ArchiveRule class. Move all processing related code to the process method like ZipTempFile ,MoveArchiveToDestinations

 
        //inside FileActionService 
        public void ArchiveRule(BackupRule rule)
        {
            ArchiveAction archiver = new ArchiveAction(rule);
            archiver.Process();
        }

       //inside ArchiveAction class 
       public void Process()
        {
            if (rule.Type == RuleType.Archive)
            {
                ZipTempFile();
                MoveArchiveToDestinations();
            }
            else if (rule.Type == RuleType.Mirror)
            {
                MirrorRule();
            }

            CleanTempDirectory();
            CleanOldBackups();
        }


 

once this is done you can further refactor your if else clause in a strategy pattern , so you will need a factory which will return class will handle the Archive rule or mirror rule inside a different class and your FileActionService will contain only processing logic

\$\endgroup\$
  • \$\begingroup\$ What is the difference of having the Process() method and just doing that in the ArchiveRule() method? I see no real advantage to moving the logic? \$\endgroup\$ – aaronmallen Jul 26 '14 at 16:36
  • 1
    \$\begingroup\$ by doing so you dont have to clutter ur code in FileActionService , so can be used by any other code which need this kind of thing \$\endgroup\$ – paritosh Jul 26 '14 at 16:38
  • \$\begingroup\$ valid... I didn't think of that. \$\endgroup\$ – aaronmallen Jul 26 '14 at 16:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.