Important Update
The Guide Feature will be discontinued after December 15th, 2023. Until then, you can continue to access and refer to the existing guides.
Author avatar

Murat Aykanat

Creating File Packages in C#

Murat Aykanat

  • Jan 10, 2019
  • 11 Min read
  • 21,951 Views
  • Jan 10, 2019
  • 11 Min read
  • 21,951 Views
Microsoft.NET

Introduction

Anytime users save or load something in an application or game, the program typically reads or writes files using the disk as a means of saving or restoring these "states." However, occasionally you cannot use a single file to store information. For instance, you may want to organize the current state into various groups, such as base text data, formatting, metadata, and so on. For that, the appropriate tool is a file package.

In this tutorial, I will explain how to generate and read packages that contain multiple files using C#. Hopefully, by the end of this guide, you will be able to understand the basics of file packages and be able to design customized packages, if need be.

What are file packages?

Before we dive into the code I will give an example of a package file so we understand what we are trying to create. Various kinds of software use file packages to save and load states. One such example is Microsoft Office. If you played around or tried to restore an Office file before, you might have found that you can open a file like this:

  1. Change the Office file extension to zip.
  2. Use your favorite compression utility to extract the file into a folder.
  3. Open the folder

We perform steps 1 and 2:

description

Then we open the folder:

description

As you can see here, a Word file contains files and directories and it is actually acts as a wrapper of the files actually read and written by Office. This all forms a file package.

Now we know what file packages are, we can start writing the code for our packaging utility.

Packaging Utility

Our packaging utility will have 3 classes.

  1. A FilePackage class to hold information about our file packages.
  2. A FilePackageWriter class to write file packages.
  3. A FilePackageReader class to read file packages.

Let's get started by creating our first class.

1public class FilePackage
2{
3    public string FilePath { get; set; }
4    public IEnumerable<string> ContentFilePathList { get; set; }
5}
csharp

This class has two properties; FilePath to hold the file path of the package and ContentFilePathList to hold the file paths of the contents of the file package. Now let's create our FilePackageWriter class to actually write our package.

1public class FilePackageWriter
2{
3    private readonly string _filepath;
4    private readonly IEnumerable<string> _contentFilePathList;
5    private string _tempDirectoryPath;
6
7    public FilePackageWriter(FilePackage filePackage)
8    {
9        _filepath = filePackage.FilePath;
10        _contentFilePathList = filePackage.ContentFilePathList;
11    }
12
13    public void GeneratePackage(bool deleteContents)
14    {
15        try
16        {
17            string parentDirectoryPath = null;
18            string filename = null;
19
20            var fileInfo = new FileInfo(_filepath);
21
22            // Get the parent directory path of the package file and if the package file already exists delete it
23            if (fileInfo.Exists)
24            {
25                filename = fileInfo.Name;
26
27                var parentDirectoryInfo = fileInfo.Directory;
28                if (parentDirectoryInfo != null)
29                {
30                    parentDirectoryPath = parentDirectoryInfo.FullName;
31                }
32                else
33                {
34                    throw new NullReferenceException("Parent directory info was null!");
35                }
36
37                File.Delete(_filepath);
38            }
39            else
40            {
41                var lastIndexOfFileSeperator = _filepath.LastIndexOf("\\", StringComparison.Ordinal);
42                if (lastIndexOfFileSeperator != -1)
43                {
44                    parentDirectoryPath = _filepath.Substring(0, lastIndexOfFileSeperator);
45                    filename = _filepath.Substring(lastIndexOfFileSeperator + 1,_filepath.Length - (lastIndexOfFileSeperator + 1));
46                }
47                else
48                {
49                    throw new Exception("The input file path '" + _filepath +
50                                        "' does not contain any file seperators.");
51                }
52            }
53
54            // Create a temp directory for our package
55            _tempDirectoryPath = parentDirectoryPath + "\\" + filename + "_temp";
56            if (Directory.Exists(_tempDirectoryPath))
57            {
58                Directory.Delete(_tempDirectoryPath, true);
59            }
60
61            Directory.CreateDirectory(_tempDirectoryPath);
62            foreach (var filePath in _contentFilePathList)
63            {
64                // Copy every content file into the temp directory we created before
65                var filePathInfo = new FileInfo(filePath);
66                if (filePathInfo.Exists)
67                {
68                    File.Copy(filePathInfo.FullName, _tempDirectoryPath + "\\" + filePathInfo.Name);
69                }
70                else
71                {
72                    throw new FileNotFoundException("File path " + filePath + " doesn't exist!");
73                }
74            }
75            // Generate the ZIP from the temp directory
76            ZipFile.CreateFromDirectory(_tempDirectoryPath, _filepath);
77        }
78        catch (Exception e)
79        {
80            var errorMessage = "An error occured while generating the package. " + e.Message;
81            throw new Exception(errorMessage);
82        }
83        finally
84        {
85            // Clear the temp directory and the content files
86            if (Directory.Exists(_tempDirectoryPath))
87            {
88                Directory.Delete(_tempDirectoryPath, true);
89            }
90
91            if (deleteContents)
92            {
93                foreach (var filePath in _contentFilePathList)
94                {
95                    if (File.Exists(filePath))
96                    {
97                        File.Delete(filePath);
98                    }
99                }
100            }
101        }
102    }
103}
csharp

In this class we simply take the FilePackage and use it to generate our package.

  1. We find the parent directory path that we want to save the package into and the filename.
  2. We create a temporary directory for our content files.
  3. We copy the content files into this temporary directory.
  4. We compress this directory and rename it as our package file.
  5. Finally we delete the temporary folder we created, and if we want we can also delete the content files if we set the deleteContents parameter as true.

Now that we can write our packages, let's write the code to read them as well.

1public class FilePackageReader
2{
3    private Dictionary<string, string> _filenameFileContentDictionary;
4    private readonly string _filepath;
5
6    public FilePackageReader(string filepath)
7    {
8        _filepath = filepath;
9    }
10
11    public Dictionary<string, string> GetFilenameFileContentDictionary()
12    {
13        try
14        {
15            _filenameFileContentDictionary = new Dictionary<string, string>();
16
17            // Open the package file
18            using (var fs = new FileStream(_filepath, FileMode.Open))
19            {
20                // Open the package file as a ZIP
21                using (var archive = new ZipArchive(fs))
22                {
23                    // Iterate through the content files and add them to a dictionary
24                    foreach (var zipArchiveEntry in archive.Entries)
25                    {
26                        using (var stream = zipArchiveEntry.Open())
27                        {
28                            using (var zipSr = new StreamReader(stream))
29                            {
30                                _filenameFileContentDictionary.Add(zipArchiveEntry.Name, zipSr.ReadToEnd());
31                            }
32                        }
33                    }
34                }
35            }
36
37            return _filenameFileContentDictionary;
38        }
39        catch (Exception e)
40        {
41            var errorMessage = "Unable to open/read the package. " + e.Message;
42            throw new Exception(errorMessage);
43        }
44    }
45}
csharp

In the reader:

  1. We get the file path of the package.
  2. We open it using a FileStream and a ZipArchive.
  3. We read the content files one by one and add the file name and the file contents to a Dictionary.

Our FilePackageWriter and FilePackageReader are finally ready. Now we can test our code and see if it works!

1var test1FilePath = AppDomain.CurrentDomain.BaseDirectory + "PackageTest\\test_1.txt";
2var test2FilePath = AppDomain.CurrentDomain.BaseDirectory + "PackageTest\\test_2.txt";
3
4using (var sw = new StreamWriter(test1FilePath))
5{
6    sw.WriteLine("test1");
7}
8
9using (var sw = new StreamWriter(test2FilePath))
10{
11    sw.WriteLine("test2");
12}
13
14var packageFilePath = AppDomain.CurrentDomain.BaseDirectory + "PackageTest\\test.pkg";
15
16var filePackage = new FilePackage
17{
18    FilePath = packageFilePath,
19    ContentFilePathList = new List<string>
20    {
21        test1FilePath, test2FilePath
22    }
23};
24
25var filePackageWriter = new FilePackageWriter(filePackage);
26filePackageWriter.GeneratePackage(true);
27
28var filePackageReader = new FilePackageReader(packageFilePath);
29var filenameFileContentDictionary = filePackageReader.GetFilenameFileContentDictionary();
30
31foreach (var keyValuePair in filenameFileContentDictionary)
32{
33    Console.WriteLine("Filename: " + keyValuePair.Key);
34    Console.WriteLine("Content: " + keyValuePair.Value);
35}
csharp

The output is:

1Filename: test_1.txt
2Content: test1
3
4Filename: test_2.txt
5Content: test2

We can also see it in the file system:

description

description

This means that our packaging algorithms worked!

Conclusion

This guide explained how to generate file packages. Packaging is worthwhile when your application needs to read or write to multiple files when saving or restoring states. File packaging can eliminate unnecessary folders for each of your save files. Instead, your files stay neatly tucked in a single file that you can use to load or save states. This approach is also helpful to users because moving saved files into backup drives or sending them via e-mail becomes a streamlined process that no longer entails searching through folders.

I hope this guide will be useful for your projects. Happy coding!