5
// Mike KrĆ¼ger <mkrueger@novell.com>
6
// Lluis Sanchez Gual <lluis@novell.com>
8
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
10
// Permission is hereby granted, free of charge, to any person obtaining
11
// a copy of this software and associated documentation files (the
12
// "Software"), to deal in the Software without restriction, including
13
// without limitation the rights to use, copy, modify, merge, publish,
14
// distribute, sublicense, and/or sell copies of the Software, and to
15
// permit persons to whom the Software is furnished to do so, subject to
16
// the following conditions:
18
// The above copyright notice and this permission notice shall be
19
// included in all copies or substantial portions of the Software.
21
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
using System.Diagnostics;
36
using MonoDevelop.Core.FileSystem;
37
using System.Collections.Generic;
38
using System.Threading;
41
namespace MonoDevelop.Core
43
public static class FileService
45
const string addinFileSystemExtensionPath = "/MonoDevelop/Core/FileSystemExtensions";
47
static FileServiceErrorHandler errorHandler;
49
static FileSystemExtension fileSystemChain;
50
static FileSystemExtension defaultExtension = Platform.IsWindows ? new DefaultFileSystemExtension () : new UnixFileSystemExtension () ;
52
static EventQueue eventQueue = new EventQueue ();
54
static string applicationRootPath = Path.Combine (PropertyService.EntryAssemblyPath, "..");
55
public static string ApplicationRootPath {
57
return applicationRootPath;
63
AddinManager.ExtensionChanged += delegate (object sender, ExtensionEventArgs args) {
64
if (args.PathChanged (addinFileSystemExtensionPath))
69
static void UpdateExtensions ()
71
if (!Runtime.Initialized) {
72
fileSystemChain = defaultExtension;
76
FileSystemExtension[] extensions = (FileSystemExtension[]) AddinManager.GetExtensionObjects (addinFileSystemExtensionPath, typeof(FileSystemExtension));
77
for (int n=0; n<extensions.Length - 1; n++) {
78
extensions [n].Next = extensions [n + 1];
81
if (extensions.Length > 0) {
82
extensions [extensions.Length - 1].Next = defaultExtension;
83
fileSystemChain = extensions [0];
85
fileSystemChain = defaultExtension;
89
public static FilePath ResolveFullPath (FilePath path)
92
return GetFileSystemForPath (path, false).ResolveFullPath (path);
93
} catch (Exception e) {
94
if (!HandleError (GettextCatalog.GetString ("Can't resolve full path {0}", path), e))
96
return FilePath.Empty;
100
public static void DeleteFile (string fileName)
102
Debug.Assert (!String.IsNullOrEmpty (fileName));
104
GetFileSystemForPath (fileName, false).DeleteFile (fileName);
105
} catch (Exception e) {
106
if (!HandleError (GettextCatalog.GetString ("Can't remove file {0}", fileName), e))
110
OnFileRemoved (new FileEventArgs (fileName, false));
113
public static void DeleteDirectory (string path)
115
Debug.Assert (!String.IsNullOrEmpty (path));
117
GetFileSystemForPath (path, true).DeleteDirectory (path);
118
} catch (Exception e) {
119
if (!HandleError (GettextCatalog.GetString ("Can't remove directory {0}", path), e))
123
OnFileRemoved (new FileEventArgs (path, true));
126
public static void RenameFile (string oldName, string newName)
128
Debug.Assert (!String.IsNullOrEmpty (oldName));
129
Debug.Assert (!String.IsNullOrEmpty (newName));
130
if (Path.GetFileName (oldName) != newName) {
131
var newPath = ((FilePath)oldName).ParentDirectory.Combine (newName);
132
InternalRenameFile (oldName, newName);
133
OnFileRenamed (new FileCopyEventArgs (oldName, newPath, false));
137
public static void RenameDirectory (string oldName, string newName)
139
Debug.Assert (!String.IsNullOrEmpty (oldName));
140
Debug.Assert (!String.IsNullOrEmpty (newName));
141
if (Path.GetFileName (oldName) != newName) {
142
string newPath = Path.Combine (Path.GetDirectoryName (oldName), newName);
143
InternalMoveDirectory (oldName, newPath);
144
OnFileRenamed (new FileCopyEventArgs (oldName, newPath, true));
145
OnFileCreated (new FileEventArgs (newPath, false));
146
OnFileRemoved (new FileEventArgs (oldName, false));
150
public static void CopyFile (string srcFile, string dstFile)
152
Debug.Assert (!String.IsNullOrEmpty (srcFile));
153
Debug.Assert (!String.IsNullOrEmpty (dstFile));
154
GetFileSystemForPath (dstFile, false).CopyFile (srcFile, dstFile, true);
155
OnFileCreated (new FileEventArgs (dstFile, false));
158
public static void MoveFile (string srcFile, string dstFile)
160
Debug.Assert (!String.IsNullOrEmpty (srcFile));
161
Debug.Assert (!String.IsNullOrEmpty (dstFile));
162
InternalMoveFile (srcFile, dstFile);
163
OnFileCreated (new FileEventArgs (dstFile, false));
164
OnFileRemoved (new FileEventArgs (srcFile, false));
167
static void InternalMoveFile (string srcFile, string dstFile)
169
Debug.Assert (!String.IsNullOrEmpty (srcFile));
170
Debug.Assert (!String.IsNullOrEmpty (dstFile));
171
FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
172
FileSystemExtension dstExt = GetFileSystemForPath (dstFile, false);
174
if (srcExt == dstExt) {
175
// Everything can be handled by the same file system
176
srcExt.MoveFile (srcFile, dstFile);
178
// If the file system of the source and dest files are
179
// different, decompose the Move operation into a Copy
180
// and Delete, so every file system can handle its part
181
dstExt.CopyFile (srcFile, dstFile, true);
182
srcExt.DeleteFile (srcFile);
186
static void InternalRenameFile (string srcFile, string newName)
188
Debug.Assert (!string.IsNullOrEmpty (srcFile));
189
Debug.Assert (!string.IsNullOrEmpty (newName));
190
FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
192
srcExt.RenameFile (srcFile, newName);
195
public static void CreateDirectory (string path)
197
if (!Directory.Exists (path)) {
198
Debug.Assert (!String.IsNullOrEmpty (path));
199
GetFileSystemForPath (path, true).CreateDirectory (path);
200
OnFileCreated (new FileEventArgs (path, true));
204
public static void CopyDirectory (string srcPath, string dstPath)
206
Debug.Assert (!String.IsNullOrEmpty (srcPath));
207
Debug.Assert (!String.IsNullOrEmpty (dstPath));
208
GetFileSystemForPath (dstPath, true).CopyDirectory (srcPath, dstPath);
211
public static void MoveDirectory (string srcPath, string dstPath)
213
Debug.Assert (!String.IsNullOrEmpty (srcPath));
214
Debug.Assert (!String.IsNullOrEmpty (dstPath));
215
InternalMoveDirectory (srcPath, dstPath);
216
OnFileCreated (new FileEventArgs (dstPath, true));
217
OnFileRemoved (new FileEventArgs (srcPath, true));
220
static void InternalMoveDirectory (string srcPath, string dstPath)
222
Debug.Assert (!String.IsNullOrEmpty (srcPath));
223
Debug.Assert (!String.IsNullOrEmpty (dstPath));
224
FileSystemExtension srcExt = GetFileSystemForPath (srcPath, true);
225
FileSystemExtension dstExt = GetFileSystemForPath (dstPath, true);
227
if (srcExt == dstExt) {
228
// Everything can be handled by the same file system
229
srcExt.MoveDirectory (srcPath, dstPath);
231
// If the file system of the source and dest files are
232
// different, decompose the Move operation into a Copy
233
// and Delete, so every file system can handle its part
234
dstExt.CopyDirectory (srcPath, dstPath);
235
srcExt.DeleteDirectory (srcPath);
239
public static bool RequestFileEdit (string fileName)
241
Debug.Assert (!String.IsNullOrEmpty (fileName));
242
return GetFileSystemForPath (fileName, false).RequestFileEdit (fileName);
245
public static void NotifyFileChanged (FilePath fileName)
247
NotifyFilesChanged (new FilePath[] { fileName });
250
public static void NotifyFilesChanged (IEnumerable<FilePath> files)
253
foreach (var fsFiles in files.GroupBy (f => GetFileSystemForPath (f, false)))
254
fsFiles.Key.NotifyFilesChanged (fsFiles);
255
OnFileChanged (new FileEventArgs (files, false));
256
} catch (Exception ex) {
257
LoggingService.LogError ("File change notification failed", ex);
261
public static void NotifyFileRemoved (string fileName)
263
NotifyFilesRemoved (new FilePath[] { fileName });
266
public static void NotifyFilesRemoved (IEnumerable<FilePath> files)
269
OnFileRemoved (new FileEventArgs (files, false));
270
} catch (Exception ex) {
271
LoggingService.LogError ("File remove notification failed", ex);
275
internal static FileSystemExtension GetFileSystemForPath (string path, bool isDirectory)
277
Debug.Assert (!String.IsNullOrEmpty (path));
278
if (fileSystemChain == null)
280
FileSystemExtension nx = fileSystemChain;
281
while (nx != null && !nx.CanHandlePath (path, isDirectory))
287
readonly static char[] separators = { Path.DirectorySeparatorChar, Path.VolumeSeparatorChar, Path.AltDirectorySeparatorChar };
288
public static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
290
if (!Path.IsPathRooted (absPath))
293
absPath = Path.GetFullPath (absPath);
294
baseDirectoryPath = Path.GetFullPath (baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar));
296
string[] bPath = baseDirectoryPath.Split (separators);
297
string[] aPath = absPath.Split (separators);
300
for (; indx < Math.Min (bPath.Length, aPath.Length); indx++) {
301
if (!bPath[indx].Equals(aPath[indx]))
308
StringBuilder result = new StringBuilder ();
310
for (int i = indx; i < bPath.Length; i++) {
311
result.Append ("..");
312
if (i + 1 < bPath.Length ||Ā aPath.Length - indx > 0)
313
result.Append (Path.DirectorySeparatorChar);
317
result.Append (String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx));
318
if (result.Length == 0)
320
return result.ToString ();
323
static bool IsSeparator (char ch)
325
return ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar;
328
public unsafe static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
330
if (!Path.IsPathRooted (absPath) || string.IsNullOrEmpty (baseDirectoryPath))
333
absPath = Path.GetFullPath (absPath);
334
baseDirectoryPath = Path.GetFullPath (baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar));
336
fixed (char* bPtr = baseDirectoryPath, aPtr = absPath) {
337
var bEnd = bPtr + baseDirectoryPath.Length;
338
var aEnd = aPtr + absPath.Length;
339
char* lastStartA = aEnd;
340
char* lastStartB = bEnd;
343
// search common base path
349
if (IsSeparator (*a)) {
357
if (a >= aEnd || IsSeparator (*a)) {
368
if (lastStartA >= aEnd)
371
// handle case a: some/path b: some/path/deeper...
373
if (IsSeparator (*b)) {
379
// look how many levels to go up into the base path
381
while (lastStartB < bEnd) {
382
if (IsSeparator (*lastStartB))
386
var size = goUpCount * 2 + goUpCount + aEnd - lastStartA;
387
var result = new char [size];
388
fixed (char* rPtr = result) {
391
for (int i = 0; i < goUpCount; i++) {
394
*(r++) = Path.DirectorySeparatorChar;
396
// copy the remaining absulute path
397
while (lastStartA < aEnd)
398
*(r++) = *(lastStartA++);
400
return new string (result);
404
public static string RelativeToAbsolutePath (string baseDirectoryPath, string relPath)
406
return Path.GetFullPath (Path.Combine (baseDirectoryPath, relPath));
409
public static bool IsValidPath (string fileName)
411
if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty)
413
if (fileName.IndexOfAny (Path.GetInvalidPathChars ()) >= 0)
418
public static bool IsValidFileName (string fileName)
420
if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty)
422
if (fileName.IndexOfAny (Path.GetInvalidFileNameChars ()) >= 0)
427
public static bool IsDirectory (string filename)
429
return Directory.Exists (filename) && (File.GetAttributes (filename) & FileAttributes.Directory) != 0;
432
public static string GetFullPath (string path)
435
throw new ArgumentNullException ("path");
436
// Note: It's not required for Path.GetFullPath (path) that path exists.
437
return Path.GetFullPath (path);
440
public static string CreateTempDirectory ()
442
Random rnd = new Random ();
445
result = Path.Combine (Path.GetTempPath (), "mdTmpDir" + rnd.Next ());
446
if (!Directory.Exists (result))
449
Directory.CreateDirectory (result);
453
public static string NormalizeRelativePath (string path)
455
string result = path.Trim (Path.DirectorySeparatorChar, ' ');
456
while (result.StartsWith ("." + Path.DirectorySeparatorChar)) {
457
result = result.Substring (2);
458
result = result.Trim (Path.DirectorySeparatorChar);
460
return result == "." ? "" : result;
5
// Mike KrĆ¼ger <mkrueger@novell.com>
6
// Lluis Sanchez Gual <lluis@novell.com>
8
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
10
// Permission is hereby granted, free of charge, to any person obtaining
11
// a copy of this software and associated documentation files (the
12
// "Software"), to deal in the Software without restriction, including
13
// without limitation the rights to use, copy, modify, merge, publish,
14
// distribute, sublicense, and/or sell copies of the Software, and to
15
// permit persons to whom the Software is furnished to do so, subject to
16
// the following conditions:
18
// The above copyright notice and this permission notice shall be
19
// included in all copies or substantial portions of the Software.
21
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
using System.Diagnostics;
36
using Mono.Unix.Native;
37
using MonoDevelop.Core.FileSystem;
38
using System.Collections.Generic;
39
using System.Threading;
41
using System.Threading.Tasks;
44
namespace MonoDevelop.Core
46
public static class FileService
48
const string addinFileSystemExtensionPath = "/MonoDevelop/Core/FileSystemExtensions";
50
static FileServiceErrorHandler errorHandler;
52
static FileSystemExtension fileSystemChain;
53
static FileSystemExtension defaultExtension = Platform.IsWindows ? new DefaultFileSystemExtension () : new UnixFileSystemExtension () ;
55
static EventQueue eventQueue = new EventQueue ();
57
static string applicationRootPath = Path.Combine (PropertyService.EntryAssemblyPath, "..");
58
public static string ApplicationRootPath {
60
return applicationRootPath;
66
AddinManager.ExtensionChanged += delegate (object sender, ExtensionEventArgs args) {
67
if (args.PathChanged (addinFileSystemExtensionPath))
73
static void UpdateExtensions ()
75
if (!Runtime.Initialized) {
76
fileSystemChain = defaultExtension;
80
FileSystemExtension[] extensions = (FileSystemExtension[]) AddinManager.GetExtensionObjects (addinFileSystemExtensionPath, typeof(FileSystemExtension));
81
for (int n=0; n<extensions.Length - 1; n++) {
82
extensions [n].Next = extensions [n + 1];
85
if (extensions.Length > 0) {
86
extensions [extensions.Length - 1].Next = defaultExtension;
87
fileSystemChain = extensions [0];
89
fileSystemChain = defaultExtension;
93
public static FilePath ResolveFullPath (FilePath path)
96
return GetFileSystemForPath (path, false).ResolveFullPath (path);
97
} catch (Exception e) {
98
if (!HandleError (GettextCatalog.GetString ("Can't resolve full path {0}", path), e))
100
return FilePath.Empty;
104
public static void DeleteFile (string fileName)
106
Debug.Assert (!String.IsNullOrEmpty (fileName));
108
GetFileSystemForPath (fileName, false).DeleteFile (fileName);
109
} catch (Exception e) {
110
if (!HandleError (GettextCatalog.GetString ("Can't remove file {0}", fileName), e))
114
OnFileRemoved (new FileEventArgs (fileName, false));
117
public static void DeleteDirectory (string path)
119
Debug.Assert (!String.IsNullOrEmpty (path));
121
GetFileSystemForPath (path, true).DeleteDirectory (path);
122
} catch (Exception e) {
123
if (!HandleError (GettextCatalog.GetString ("Can't remove directory {0}", path), e))
127
OnFileRemoved (new FileEventArgs (path, true));
130
public static void RenameFile (string oldName, string newName)
132
Debug.Assert (!String.IsNullOrEmpty (oldName));
133
Debug.Assert (!String.IsNullOrEmpty (newName));
134
if (Path.GetFileName (oldName) != newName) {
135
var newPath = ((FilePath)oldName).ParentDirectory.Combine (newName);
136
InternalRenameFile (oldName, newName);
137
OnFileRenamed (new FileCopyEventArgs (oldName, newPath, false));
141
public static void RenameDirectory (string oldName, string newName)
143
Debug.Assert (!String.IsNullOrEmpty (oldName));
144
Debug.Assert (!String.IsNullOrEmpty (newName));
145
if (Path.GetFileName (oldName) != newName) {
146
string newPath = Path.Combine (Path.GetDirectoryName (oldName), newName);
147
InternalMoveDirectory (oldName, newPath);
148
OnFileRenamed (new FileCopyEventArgs (oldName, newPath, true));
149
OnFileCreated (new FileEventArgs (newPath, false));
150
OnFileRemoved (new FileEventArgs (oldName, false));
154
public static void CopyFile (string srcFile, string dstFile)
156
Debug.Assert (!String.IsNullOrEmpty (srcFile));
157
Debug.Assert (!String.IsNullOrEmpty (dstFile));
158
GetFileSystemForPath (dstFile, false).CopyFile (srcFile, dstFile, true);
159
OnFileCreated (new FileEventArgs (dstFile, false));
162
public static void MoveFile (string srcFile, string dstFile)
164
Debug.Assert (!String.IsNullOrEmpty (srcFile));
165
Debug.Assert (!String.IsNullOrEmpty (dstFile));
166
InternalMoveFile (srcFile, dstFile);
167
OnFileCreated (new FileEventArgs (dstFile, false));
168
OnFileRemoved (new FileEventArgs (srcFile, false));
171
static void InternalMoveFile (string srcFile, string dstFile)
173
Debug.Assert (!String.IsNullOrEmpty (srcFile));
174
Debug.Assert (!String.IsNullOrEmpty (dstFile));
175
FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
176
FileSystemExtension dstExt = GetFileSystemForPath (dstFile, false);
178
if (srcExt == dstExt) {
179
// Everything can be handled by the same file system
180
srcExt.MoveFile (srcFile, dstFile);
182
// If the file system of the source and dest files are
183
// different, decompose the Move operation into a Copy
184
// and Delete, so every file system can handle its part
185
dstExt.CopyFile (srcFile, dstFile, true);
186
srcExt.DeleteFile (srcFile);
190
static void InternalRenameFile (string srcFile, string newName)
192
Debug.Assert (!string.IsNullOrEmpty (srcFile));
193
Debug.Assert (!string.IsNullOrEmpty (newName));
194
FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
196
srcExt.RenameFile (srcFile, newName);
199
public static void CreateDirectory (string path)
201
if (!Directory.Exists (path)) {
202
Debug.Assert (!String.IsNullOrEmpty (path));
203
GetFileSystemForPath (path, true).CreateDirectory (path);
204
OnFileCreated (new FileEventArgs (path, true));
208
public static void CopyDirectory (string srcPath, string dstPath)
210
Debug.Assert (!String.IsNullOrEmpty (srcPath));
211
Debug.Assert (!String.IsNullOrEmpty (dstPath));
212
GetFileSystemForPath (dstPath, true).CopyDirectory (srcPath, dstPath);
215
public static void MoveDirectory (string srcPath, string dstPath)
217
Debug.Assert (!String.IsNullOrEmpty (srcPath));
218
Debug.Assert (!String.IsNullOrEmpty (dstPath));
219
InternalMoveDirectory (srcPath, dstPath);
220
OnFileCreated (new FileEventArgs (dstPath, true));
221
OnFileRemoved (new FileEventArgs (srcPath, true));
224
static void InternalMoveDirectory (string srcPath, string dstPath)
226
Debug.Assert (!String.IsNullOrEmpty (srcPath));
227
Debug.Assert (!String.IsNullOrEmpty (dstPath));
228
FileSystemExtension srcExt = GetFileSystemForPath (srcPath, true);
229
FileSystemExtension dstExt = GetFileSystemForPath (dstPath, true);
231
if (srcExt == dstExt) {
232
// Everything can be handled by the same file system
233
srcExt.MoveDirectory (srcPath, dstPath);
235
// If the file system of the source and dest files are
236
// different, decompose the Move operation into a Copy
237
// and Delete, so every file system can handle its part
238
dstExt.CopyDirectory (srcPath, dstPath);
239
srcExt.DeleteDirectory (srcPath);
243
public static bool RequestFileEdit (string fileName)
245
Debug.Assert (!String.IsNullOrEmpty (fileName));
246
return GetFileSystemForPath (fileName, false).RequestFileEdit (fileName);
249
public static void NotifyFileChanged (FilePath fileName)
251
NotifyFilesChanged (new FilePath[] { fileName });
254
public static void NotifyFilesChanged (IEnumerable<FilePath> files)
257
foreach (var fsFiles in files.GroupBy (f => GetFileSystemForPath (f, false)))
258
fsFiles.Key.NotifyFilesChanged (fsFiles);
259
OnFileChanged (new FileEventArgs (files, false));
260
} catch (Exception ex) {
261
LoggingService.LogError ("File change notification failed", ex);
265
public static void NotifyFileRemoved (string fileName)
267
NotifyFilesRemoved (new FilePath[] { fileName });
270
public static void NotifyFilesRemoved (IEnumerable<FilePath> files)
273
OnFileRemoved (new FileEventArgs (files, false));
274
} catch (Exception ex) {
275
LoggingService.LogError ("File remove notification failed", ex);
279
internal static FileSystemExtension GetFileSystemForPath (string path, bool isDirectory)
281
Debug.Assert (!String.IsNullOrEmpty (path));
282
FileSystemExtension nx = fileSystemChain;
283
while (nx != null && !nx.CanHandlePath (path, isDirectory))
289
readonly static char[] separators = { Path.DirectorySeparatorChar, Path.VolumeSeparatorChar, Path.AltDirectorySeparatorChar };
290
public static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
292
if (!Path.IsPathRooted (absPath))
295
absPath = Path.GetFullPath (absPath);
296
baseDirectoryPath = Path.GetFullPath (baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar));
298
string[] bPath = baseDirectoryPath.Split (separators);
299
string[] aPath = absPath.Split (separators);
302
for (; indx < Math.Min (bPath.Length, aPath.Length); indx++) {
303
if (!bPath[indx].Equals(aPath[indx]))
310
StringBuilder result = new StringBuilder ();
312
for (int i = indx; i < bPath.Length; i++) {
313
result.Append ("..");
314
if (i + 1 < bPath.Length ||Ā aPath.Length - indx > 0)
315
result.Append (Path.DirectorySeparatorChar);
319
result.Append (String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx));
320
if (result.Length == 0)
322
return result.ToString ();
325
static bool IsSeparator (char ch)
327
return ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar;
330
public unsafe static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
332
if (!Path.IsPathRooted (absPath) || string.IsNullOrEmpty (baseDirectoryPath))
335
absPath = Path.GetFullPath (absPath);
336
baseDirectoryPath = Path.GetFullPath (baseDirectoryPath).TrimEnd (Path.DirectorySeparatorChar);
338
fixed (char* bPtr = baseDirectoryPath, aPtr = absPath) {
339
var bEnd = bPtr + baseDirectoryPath.Length;
340
var aEnd = aPtr + absPath.Length;
341
char* lastStartA = aEnd;
342
char* lastStartB = bEnd;
345
// search common base path
351
if (IsSeparator (*a)) {
359
if (a >= aEnd || IsSeparator (*a)) {
370
if (lastStartA >= aEnd)
373
// handle case a: some/path b: some/path/deeper...
375
if (IsSeparator (*b)) {
381
// look how many levels to go up into the base path
383
while (lastStartB < bEnd) {
384
if (IsSeparator (*lastStartB))
388
var size = goUpCount * 2 + goUpCount + aEnd - lastStartA;
389
var result = new char [size];
390
fixed (char* rPtr = result) {
393
for (int i = 0; i < goUpCount; i++) {
396
*(r++) = Path.DirectorySeparatorChar;
398
// copy the remaining absulute path
399
while (lastStartA < aEnd)
400
*(r++) = *(lastStartA++);
402
return new string (result);
406
public static string RelativeToAbsolutePath (string baseDirectoryPath, string relPath)
408
return Path.GetFullPath (Path.Combine (baseDirectoryPath, relPath));
411
public static bool IsValidPath (string fileName)
413
if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty)
415
if (fileName.IndexOfAny (Path.GetInvalidPathChars ()) >= 0)
420
public static bool IsValidFileName (string fileName)
422
if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty)
424
if (fileName.IndexOfAny (Path.GetInvalidFileNameChars ()) >= 0)
429
public static bool IsDirectory (string filename)
431
return Directory.Exists (filename) && (File.GetAttributes (filename) & FileAttributes.Directory) != 0;
434
public static string GetFullPath (string path)
437
throw new ArgumentNullException ("path");
438
// Note: It's not required for Path.GetFullPath (path) that path exists.
439
return Path.GetFullPath (path);
442
public static string CreateTempDirectory ()
444
Random rnd = new Random ();
447
result = Path.Combine (Path.GetTempPath (), "mdTmpDir" + rnd.Next ());
448
if (!Directory.Exists (result))
451
Directory.CreateDirectory (result);
455
public static string NormalizeRelativePath (string path)
457
string result = path.Trim (Path.DirectorySeparatorChar, ' ');
458
while (result.StartsWith ("." + Path.DirectorySeparatorChar)) {
459
result = result.Substring (2);
460
result = result.Trim (Path.DirectorySeparatorChar);
462
return result == "." ? "" : result;
463
465
// Atomic rename of a file. It does not fire events.
464
466
public static void SystemRename (string sourceFile, string destFile)
468
if (string.IsNullOrEmpty (sourceFile))
469
throw new ArgumentException ("sourceFile");
471
if (string.IsNullOrEmpty (destFile))
472
throw new ArgumentException ("destFile");
466
474
//FIXME: use the atomic System.IO.File.Replace on NTFS
467
475
if (Platform.IsWindows) {
468
476
string wtmp = null;
499
Mono.Unix.Native.Syscall.rename (sourceFile, destFile);
504
/// Removes the directory if it's empty.
506
public static void RemoveDirectoryIfEmpty (string directory)
508
if (Directory.Exists (directory) && !Directory.EnumerateFiles (directory).Any ())
509
Directory.Delete (directory);
513
/// Creates a directory if it does not already exist.
515
public static void EnsureDirectoryExists (string directory)
517
if (!Directory.Exists (directory))
518
Directory.CreateDirectory (directory);
522
/// Makes the path separators native.
524
public static string MakePathSeparatorsNative (string path)
526
if (path == null || path.Length == 0)
528
char c = Path.DirectorySeparatorChar == '\\'? '/' : '\\';
529
return path.Replace (c, Path.DirectorySeparatorChar);
532
static bool HandleError (string message, Exception ex)
534
return errorHandler != null ? errorHandler (message, ex) : false;
537
public static FileServiceErrorHandler ErrorHandler {
538
get { return errorHandler; }
539
set { errorHandler = value; }
542
public static void FreezeEvents ()
544
eventQueue.Freeze ();
547
public static void ThawEvents ()
552
public static event EventHandler<FileEventArgs> FileCreated;
553
static void OnFileCreated (FileEventArgs args)
555
foreach (FileEventInfo fi in args) {
557
Counters.DirectoriesCreated++;
559
Counters.FilesCreated++;
562
eventQueue.RaiseEvent (() => FileCreated, args);
565
public static event EventHandler<FileCopyEventArgs> FileRenamed;
566
static void OnFileRenamed (FileCopyEventArgs args)
568
foreach (FileCopyEventInfo fi in args) {
570
Counters.DirectoriesRenamed++;
572
Counters.FilesRenamed++;
575
eventQueue.RaiseEvent (() => FileRenamed, args);
578
public static event EventHandler<FileEventArgs> FileRemoved;
579
static void OnFileRemoved (FileEventArgs args)
581
foreach (FileEventInfo fi in args) {
583
Counters.DirectoriesRemoved++;
585
Counters.FilesRemoved++;
588
eventQueue.RaiseEvent (() => FileRemoved, args);
591
public static event EventHandler<FileEventArgs> FileChanged;
592
static void OnFileChanged (FileEventArgs args)
594
Counters.FileChangeNotifications++;
595
eventQueue.RaiseEvent (() => FileChanged, null, args);
603
public Func<Delegate> Delegate;
604
public object ThisObject;
605
public EventArgs Args;
608
List<EventData> events = new List<EventData> ();
611
object defaultSourceObject;
617
public EventQueue (object defaultSourceObject)
619
this.defaultSourceObject = defaultSourceObject;
622
public void Freeze ()
631
List<EventData> pendingEvents = null;
634
pendingEvents = events;
635
events = new List<EventData> ();
638
if (pendingEvents != null) {
639
for (int n=0; n<pendingEvents.Count; n++) {
640
EventData ev = pendingEvents [n];
641
Delegate del = ev.Delegate ();
642
if (ev.Args is IEventArgsChain) {
643
EventData next = n < pendingEvents.Count - 1 ? pendingEvents [n + 1] : null;
644
if (next != null && (next.Args.GetType() == ev.Args.GetType ()) && next.Delegate() == del && next.ThisObject == ev.ThisObject) {
645
((IEventArgsChain)next.Args).MergeWith ((IEventArgsChain)ev.Args);
650
del.DynamicInvoke (ev.ThisObject, ev.Args);
655
public void RaiseEvent (Func<Delegate> d, EventArgs args)
657
RaiseEvent (d, defaultSourceObject, args);
660
public void RaiseEvent (Func<Delegate> d, object thisObj, EventArgs args)
665
EventData ed = new EventData ();
667
ed.ThisObject = thisObj;
674
del.DynamicInvoke (thisObj, args);
678
public delegate bool FileServiceErrorHandler (string message, Exception ex);
507
if (Syscall.rename (sourceFile, destFile) != 0) {
508
switch (Stdlib.GetLastError ()) {
511
throw new UnauthorizedAccessException ();
513
throw new InvalidOperationException ();
515
throw new DirectoryNotFoundException ();
517
throw new FileNotFoundException ();
518
case Errno.ENAMETOOLONG:
519
throw new PathTooLongException ();
521
throw new IOException ();
528
/// Removes the directory if it's empty.
530
public static void RemoveDirectoryIfEmpty (string directory)
532
// HACK: we should use EnumerateFiles but it's broken in some Mono releases
533
// https://bugzilla.xamarin.com/show_bug.cgi?id=2975
534
if (Directory.Exists (directory) && !Directory.GetFiles (directory).Any ())
535
Directory.Delete (directory);
539
/// Creates a directory if it does not already exist.
541
public static void EnsureDirectoryExists (string directory)
543
if (!Directory.Exists (directory))
544
Directory.CreateDirectory (directory);
548
/// Makes the path separators native.
550
public static string MakePathSeparatorsNative (string path)
552
if (path == null || path.Length == 0)
554
char c = Path.DirectorySeparatorChar == '\\'? '/' : '\\';
555
return path.Replace (c, Path.DirectorySeparatorChar);
558
static bool HandleError (string message, Exception ex)
560
return errorHandler != null ? errorHandler (message, ex) : false;
563
public static FileServiceErrorHandler ErrorHandler {
564
get { return errorHandler; }
565
set { errorHandler = value; }
568
public static void FreezeEvents ()
570
eventQueue.Freeze ();
573
public static void ThawEvents ()
578
public static event EventHandler<FileEventArgs> FileCreated;
579
static void OnFileCreated (FileEventArgs args)
581
foreach (FileEventInfo fi in args) {
583
Counters.DirectoriesCreated++;
585
Counters.FilesCreated++;
588
eventQueue.RaiseEvent (() => FileCreated, args);
591
public static event EventHandler<FileCopyEventArgs> FileRenamed;
592
static void OnFileRenamed (FileCopyEventArgs args)
594
foreach (FileCopyEventInfo fi in args) {
596
Counters.DirectoriesRenamed++;
598
Counters.FilesRenamed++;
601
eventQueue.RaiseEvent (() => FileRenamed, args);
604
public static event EventHandler<FileEventArgs> FileRemoved;
605
static void OnFileRemoved (FileEventArgs args)
607
foreach (FileEventInfo fi in args) {
609
Counters.DirectoriesRemoved++;
611
Counters.FilesRemoved++;
614
eventQueue.RaiseEvent (() => FileRemoved, args);
617
public static event EventHandler<FileEventArgs> FileChanged;
618
static void OnFileChanged (FileEventArgs args)
620
Counters.FileChangeNotifications++;
621
eventQueue.RaiseEvent (() => FileChanged, null, args);
624
public static Task<bool> UpdateDownloadedCacheFile (string url, string cacheFile,
625
Func<Stream,bool> validateDownload = null, CancellationToken ct = new CancellationToken ())
627
var tcs = new TaskCompletionSource<bool> ();
629
//HACK: .NET blocks on DNS in BeginGetResponse, so use a threadpool thread
630
// see http://stackoverflow.com/questions/1232139#1232930
631
System.Threading.ThreadPool.QueueUserWorkItem ((state) => {
632
var request = (HttpWebRequest)WebRequest.Create (url);
635
//check to see if the online file has been modified since it was last downloaded
636
var localNewsXml = new FileInfo (cacheFile);
637
if (localNewsXml.Exists)
638
request.IfModifiedSince = localNewsXml.LastWriteTime;
640
request.BeginGetResponse (HandleResponse, new CacheFileDownload {
643
CacheFile = cacheFile,
644
ValidateDownload = validateDownload,
645
CancellationToken = ct,
648
} catch (Exception ex) {
649
tcs.SetException (ex);
656
class CacheFileDownload
658
public HttpWebRequest Request;
659
public string CacheFile, Url;
660
public Func<Stream,bool> ValidateDownload;
661
public CancellationToken CancellationToken;
662
public TaskCompletionSource<bool> Tcs;
665
static void HandleResponse (IAsyncResult ar)
667
var c = (CacheFileDownload) ar.AsyncState;
668
bool deleteTempFile = true;
669
var tempFile = c.CacheFile + ".temp";
672
if (c.CancellationToken.IsCancellationRequested)
673
c.Tcs.TrySetCanceled ();
676
//TODO: limit this size in case open wifi hotspots provide bad data
677
var response = (HttpWebResponse) c.Request.EndGetResponse (ar);
678
if (response.StatusCode == HttpStatusCode.OK) {
679
using (var fs = File.Create (tempFile))
680
response.GetResponseStream ().CopyTo (fs, 2048);
682
} catch (WebException wex) {
683
var httpResp = wex.Response as HttpWebResponse;
684
if (httpResp != null) {
685
if (httpResp.StatusCode == HttpStatusCode.NotModified) {
686
c.Tcs.TrySetResult (false);
689
//is this valid? should we just return the WebException directly?
690
else if (httpResp.StatusCode == HttpStatusCode.NotFound) {
691
c.Tcs.TrySetException (new FileNotFoundException ("File not found on server", c.Url, wex));
698
//check the document is valid, might get bad ones from wifi hotspots etc
699
if (c.ValidateDownload != null) {
700
if (c.CancellationToken.IsCancellationRequested)
701
c.Tcs.TrySetCanceled ();
703
using (var f = File.OpenRead (tempFile)) {
705
if (!c.ValidateDownload (f)) {
706
c.Tcs.TrySetException (new Exception ("Failed to validate downloaded file"));
709
} catch (Exception ex) {
710
c.Tcs.TrySetException (new Exception ("Failed to validate downloaded file", ex));
715
if (c.CancellationToken.IsCancellationRequested)
716
c.Tcs.TrySetCanceled ();
718
SystemRename (tempFile, c.CacheFile);
719
deleteTempFile = false;
720
c.Tcs.TrySetResult (true);
721
} catch (Exception ex) {
722
c.Tcs.TrySetException (ex);
724
if (deleteTempFile) {
726
File.Delete (tempFile);
737
public Func<Delegate> Delegate;
738
public object ThisObject;
739
public EventArgs Args;
742
List<EventData> events = new List<EventData> ();
745
object defaultSourceObject;
751
public EventQueue (object defaultSourceObject)
753
this.defaultSourceObject = defaultSourceObject;
756
public void Freeze ()
765
List<EventData> pendingEvents = null;
768
pendingEvents = events;
769
events = new List<EventData> ();
772
if (pendingEvents != null) {
773
for (int n=0; n<pendingEvents.Count; n++) {
774
EventData ev = pendingEvents [n];
775
Delegate del = ev.Delegate ();
776
if (ev.Args is IEventArgsChain) {
777
EventData next = n < pendingEvents.Count - 1 ? pendingEvents [n + 1] : null;
778
if (next != null && (next.Args.GetType() == ev.Args.GetType ()) && next.Delegate() == del && next.ThisObject == ev.ThisObject) {
779
((IEventArgsChain)next.Args).MergeWith ((IEventArgsChain)ev.Args);
784
del.DynamicInvoke (ev.ThisObject, ev.Args);
789
public void RaiseEvent (Func<Delegate> d, EventArgs args)
791
RaiseEvent (d, defaultSourceObject, args);
794
public void RaiseEvent (Func<Delegate> d, object thisObj, EventArgs args)
799
EventData ed = new EventData ();
801
ed.ThisObject = thisObj;
808
del.DynamicInvoke (thisObj, args);
812
public delegate bool FileServiceErrorHandler (string message, Exception ex);