1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections;
6 using System.IO;
7 
8 // We keep this class in Common to allow utilizing it without taking a dependency on System.CodeDom
9 #if CODEDOM
10 namespace System.CodeDom.Compiler
11 #else
12 namespace System.IO.Internal
13 #endif
14 {
15     // Explicitly not [Serializable], so as to avoid accidentally deleting
16     // files specified in a serialized payload.
17 
18 #if MONO
19     [Serializable]
20 #endif
21 
22 #if CODEDOM
23     public
24 #else
25     internal
26 #endif
27     class TempFileCollection : ICollection, IDisposable
28     {
29         private string _basePath;
30         private readonly string _tempDir;
31         private readonly Hashtable _files;
32 
TempFileCollection()33         public TempFileCollection() : this(null, false)
34         {
35         }
36 
TempFileCollection(string tempDir)37         public TempFileCollection(string tempDir) : this(tempDir, false)
38         {
39         }
40 
TempFileCollection(string tempDir, bool keepFiles)41         public TempFileCollection(string tempDir, bool keepFiles)
42         {
43             KeepFiles = keepFiles;
44             _tempDir = tempDir;
45             _files = new Hashtable(StringComparer.OrdinalIgnoreCase);
46         }
47 
IDisposable.Dispose()48         void IDisposable.Dispose()
49         {
50             Dispose(true);
51             GC.SuppressFinalize(this);
52         }
53 
Dispose(bool disposing)54         protected virtual void Dispose(bool disposing)
55         {
56             SafeDelete();
57         }
58 
~TempFileCollection()59         ~TempFileCollection()
60         {
61             Dispose(false);
62         }
63 
AddExtension(string fileExtension)64         public string AddExtension(string fileExtension) => AddExtension(fileExtension, KeepFiles);
65 
AddExtension(string fileExtension, bool keepFile)66         public string AddExtension(string fileExtension, bool keepFile)
67         {
68             if (string.IsNullOrEmpty(fileExtension))
69             {
70                 throw new ArgumentException(SR.Format(SR.InvalidNullEmptyArgument, nameof(fileExtension)), nameof(fileExtension));
71             }
72 
73             string fileName = BasePath + "." + fileExtension;
74             AddFile(fileName, keepFile);
75             return fileName;
76         }
77 
AddFile(string fileName, bool keepFile)78         public void AddFile(string fileName, bool keepFile)
79         {
80             if (string.IsNullOrEmpty(fileName))
81             {
82                 throw new ArgumentException(SR.Format(SR.InvalidNullEmptyArgument, nameof(fileName)), nameof(fileName));
83             }
84 
85             if (_files[fileName] != null)
86             {
87                 throw new ArgumentException(SR.Format(SR.DuplicateFileName, fileName), nameof(fileName));
88             }
89 
90             _files.Add(fileName, keepFile);
91         }
92 
GetEnumerator()93         public IEnumerator GetEnumerator() => _files.Keys.GetEnumerator();
94 
IEnumerable.GetEnumerator()95         IEnumerator IEnumerable.GetEnumerator() => _files.Keys.GetEnumerator();
96 
ICollection.CopyTo(Array array, int start)97         void ICollection.CopyTo(Array array, int start) => _files.Keys.CopyTo(array, start);
98 
CopyTo(string[] fileNames, int start)99         public void CopyTo(string[] fileNames, int start) => _files.Keys.CopyTo(fileNames, start);
100 
101         public int Count => _files.Count;
102 
103         int ICollection.Count => _files.Count;
104 
105         object ICollection.SyncRoot => null;
106 
107         bool ICollection.IsSynchronized => false;
108 
109         public string TempDir => _tempDir ?? string.Empty;
110 
111         public string BasePath
112         {
113             get
114             {
115                 EnsureTempNameCreated();
116                 return _basePath;
117             }
118         }
119 
EnsureTempNameCreated()120         private void EnsureTempNameCreated()
121         {
122             if (_basePath == null)
123             {
124                 string tempFileName = null;
125                 bool uniqueFile = false;
126                 int retryCount = 5000;
127                 do
128                 {
129                     _basePath = Path.Combine(
130                         string.IsNullOrEmpty(TempDir) ? Path.GetTempPath() : TempDir,
131                         Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
132                     tempFileName = _basePath + ".tmp";
133 
134                     try
135                     {
136                         new FileStream(tempFileName, FileMode.CreateNew, FileAccess.Write).Dispose();
137                         uniqueFile = true;
138                     }
139                     catch (IOException ex)
140                     {
141                         retryCount--;
142                         if (retryCount == 0 || ex is DirectoryNotFoundException)
143                         {
144                             throw;
145                         }
146                         uniqueFile = false;
147                     }
148                 } while (!uniqueFile);
149                 _files.Add(tempFileName, KeepFiles);
150             }
151         }
152 
153         public bool KeepFiles { get; set; }
154 
KeepFile(string fileName)155         private bool KeepFile(string fileName)
156         {
157             object keep = _files[fileName];
158             return keep != null ? (bool)keep : false;
159         }
160 
Delete()161         public void Delete()
162         {
163             SafeDelete();
164         }
165 
Delete(string fileName)166         internal void Delete(string fileName)
167         {
168             try
169             {
170                 File.Delete(fileName);
171             }
172             catch
173             {
174                 // Ignore all exceptions
175             }
176         }
177 
SafeDelete()178         internal void SafeDelete()
179         {
180             if (_files != null && _files.Count > 0)
181             {
182                 string[] fileNames = new string[_files.Count];
183                 _files.Keys.CopyTo(fileNames, 0);
184                 foreach (string fileName in fileNames)
185                 {
186                     if (!KeepFile(fileName))
187                     {
188                         Delete(fileName);
189                         _files.Remove(fileName);
190                     }
191                 }
192             }
193         }
194     }
195 }
196