~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
//
2
 
// FileService.cs
3
 
//
4
 
// Author:
5
 
//   Mike KrĆ¼ger <mkrueger@novell.com>
6
 
//   Lluis Sanchez Gual <lluis@novell.com>
7
 
//
8
 
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
9
 
//
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:
17
 
// 
18
 
// The above copyright notice and this permission notice shall be
19
 
// included in all copies or substantial portions of the Software.
20
 
// 
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.
28
 
//
29
 
 
30
 
using System;
31
 
using System.Diagnostics;
32
 
using System.IO;
33
 
using System.Text;
34
 
 
35
 
using Mono.Addins;
36
 
using MonoDevelop.Core.FileSystem;
37
 
using System.Collections.Generic;
38
 
using System.Threading;
39
 
using System.Linq;
40
 
 
41
 
namespace MonoDevelop.Core
42
 
{
43
 
        public static class FileService
44
 
        {
45
 
                const string addinFileSystemExtensionPath = "/MonoDevelop/Core/FileSystemExtensions";
46
 
 
47
 
                static FileServiceErrorHandler errorHandler;
48
 
                
49
 
                static FileSystemExtension fileSystemChain;
50
 
                static FileSystemExtension defaultExtension = Platform.IsWindows ? new DefaultFileSystemExtension () : new UnixFileSystemExtension () ;
51
 
                
52
 
                static EventQueue eventQueue = new EventQueue ();
53
 
                
54
 
                static string applicationRootPath = Path.Combine (PropertyService.EntryAssemblyPath, "..");
55
 
                public static string ApplicationRootPath {
56
 
                        get {
57
 
                                return applicationRootPath;
58
 
                        }
59
 
                }
60
 
                
61
 
                static FileService()
62
 
                {
63
 
                        AddinManager.ExtensionChanged += delegate (object sender, ExtensionEventArgs args) {
64
 
                                if (args.PathChanged (addinFileSystemExtensionPath))
65
 
                                        UpdateExtensions ();
66
 
                        };
67
 
                }
68
 
                
69
 
                static void UpdateExtensions ()
70
 
                {
71
 
                        if (!Runtime.Initialized) {
72
 
                                fileSystemChain = defaultExtension;
73
 
                                return;
74
 
                        }
75
 
                        
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];
79
 
                        }
80
 
 
81
 
                        if (extensions.Length > 0) {
82
 
                                extensions [extensions.Length - 1].Next = defaultExtension;
83
 
                                fileSystemChain = extensions [0];
84
 
                        } elseĀ {
85
 
                                fileSystemChain = defaultExtension;
86
 
                        }
87
 
                }
88
 
                
89
 
                public static FilePath ResolveFullPath (FilePath path)
90
 
                {
91
 
                        try {
92
 
                                return GetFileSystemForPath (path, false).ResolveFullPath (path);
93
 
                        } catch (Exception e) {
94
 
                                if (!HandleError (GettextCatalog.GetString ("Can't resolve full path {0}", path), e))
95
 
                                        throw;
96
 
                                return FilePath.Empty;
97
 
                        }
98
 
                }
99
 
                
100
 
                public static void DeleteFile (string fileName)
101
 
                {
102
 
                        Debug.Assert (!String.IsNullOrEmpty (fileName));
103
 
                        try {
104
 
                                GetFileSystemForPath (fileName, false).DeleteFile (fileName);
105
 
                        } catch (Exception e) {
106
 
                                if (!HandleError (GettextCatalog.GetString ("Can't remove file {0}", fileName), e))
107
 
                                        throw;
108
 
                                return;
109
 
                        }
110
 
                        OnFileRemoved (new FileEventArgs (fileName, false));
111
 
                }
112
 
                
113
 
                public static void DeleteDirectory (string path)
114
 
                {
115
 
                        Debug.Assert (!String.IsNullOrEmpty (path));
116
 
                        try {
117
 
                                GetFileSystemForPath (path, true).DeleteDirectory (path);
118
 
                        } catch (Exception e) {
119
 
                                if (!HandleError (GettextCatalog.GetString ("Can't remove directory {0}", path), e))
120
 
                                        throw;
121
 
                                return;
122
 
                        }
123
 
                        OnFileRemoved (new FileEventArgs (path, true));
124
 
                }
125
 
                
126
 
                public static void RenameFile (string oldName, string newName)
127
 
                {
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));
134
 
                        }
135
 
                }
136
 
                
137
 
                public static void RenameDirectory (string oldName, string newName)
138
 
                {
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));
147
 
                        }
148
 
                }
149
 
                
150
 
                public static void CopyFile (string srcFile, string dstFile)
151
 
                {
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));
156
 
                }
157
 
 
158
 
                public static void MoveFile (string srcFile, string dstFile)
159
 
                {
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));
165
 
                }
166
 
                
167
 
                static void InternalMoveFile (string srcFile, string dstFile)
168
 
                {
169
 
                        Debug.Assert (!String.IsNullOrEmpty (srcFile));
170
 
                        Debug.Assert (!String.IsNullOrEmpty (dstFile));
171
 
                        FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
172
 
                        FileSystemExtension dstExt = GetFileSystemForPath (dstFile, false);
173
 
                        
174
 
                        if (srcExt == dstExt) {
175
 
                                // Everything can be handled by the same file system
176
 
                                srcExt.MoveFile (srcFile, dstFile);
177
 
                        } else {
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);
183
 
                        }
184
 
                }
185
 
                
186
 
                static void InternalRenameFile (string srcFile, string newName)
187
 
                {
188
 
                        Debug.Assert (!string.IsNullOrEmpty (srcFile));
189
 
                        Debug.Assert (!string.IsNullOrEmpty (newName));
190
 
                        FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
191
 
                        
192
 
                        srcExt.RenameFile (srcFile, newName);
193
 
                }
194
 
                
195
 
                public static void CreateDirectory (string path)
196
 
                {
197
 
                        if (!Directory.Exists (path)) {
198
 
                                Debug.Assert (!String.IsNullOrEmpty (path));
199
 
                                GetFileSystemForPath (path, true).CreateDirectory (path);
200
 
                                OnFileCreated (new FileEventArgs (path, true));
201
 
                        }
202
 
                }
203
 
                
204
 
                public static void CopyDirectory (string srcPath, string dstPath)
205
 
                {
206
 
                        Debug.Assert (!String.IsNullOrEmpty (srcPath));
207
 
                        Debug.Assert (!String.IsNullOrEmpty (dstPath));
208
 
                        GetFileSystemForPath (dstPath, true).CopyDirectory (srcPath, dstPath);
209
 
                }
210
 
                
211
 
                public static void MoveDirectory (string srcPath, string dstPath)
212
 
                {
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));
218
 
                }
219
 
                
220
 
                static void InternalMoveDirectory (string srcPath, string dstPath)
221
 
                {
222
 
                        Debug.Assert (!String.IsNullOrEmpty (srcPath));
223
 
                        Debug.Assert (!String.IsNullOrEmpty (dstPath));
224
 
                        FileSystemExtension srcExt = GetFileSystemForPath (srcPath, true);
225
 
                        FileSystemExtension dstExt = GetFileSystemForPath (dstPath, true);
226
 
                        
227
 
                        if (srcExt == dstExt) {
228
 
                                // Everything can be handled by the same file system
229
 
                                srcExt.MoveDirectory (srcPath, dstPath);
230
 
                        } else {
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);
236
 
                        }
237
 
                }
238
 
                
239
 
                public static bool RequestFileEdit (string fileName)
240
 
                {
241
 
                        Debug.Assert (!String.IsNullOrEmpty (fileName));
242
 
                        return GetFileSystemForPath (fileName, false).RequestFileEdit (fileName);
243
 
                }
244
 
                
245
 
                public static void NotifyFileChanged (FilePath fileName)
246
 
                {
247
 
                        NotifyFilesChanged (new FilePath[] { fileName });
248
 
                }
249
 
                
250
 
                public static void NotifyFilesChanged (IEnumerable<FilePath> files)
251
 
                {
252
 
                        try {
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);
258
 
                        }
259
 
                }
260
 
                
261
 
                public static void NotifyFileRemoved (string fileName)
262
 
                {
263
 
                        NotifyFilesRemoved (new FilePath[] { fileName });
264
 
                }
265
 
                
266
 
                public static void NotifyFilesRemoved (IEnumerable<FilePath> files)
267
 
                {
268
 
                        try {
269
 
                                OnFileRemoved (new FileEventArgs (files, false));
270
 
                        } catch (Exception ex) {
271
 
                                LoggingService.LogError ("File remove notification failed", ex);
272
 
                        }
273
 
                }
274
 
                
275
 
                internal static FileSystemExtension GetFileSystemForPath (string path, bool isDirectory)
276
 
                {
277
 
                        Debug.Assert (!String.IsNullOrEmpty (path));
278
 
                        if (fileSystemChain == null)
279
 
                                UpdateExtensions ();
280
 
                        FileSystemExtension nx = fileSystemChain;
281
 
                        while (nx != null && !nx.CanHandlePath (path, isDirectory))
282
 
                                nx = nx.Next;
283
 
                        return nx;
284
 
                }
285
 
                
286
 
/*
287
 
                readonly static char[] separators = { Path.DirectorySeparatorChar, Path.VolumeSeparatorChar, Path.AltDirectorySeparatorChar };
288
 
                public static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
289
 
                {
290
 
                        if (!Path.IsPathRooted (absPath))
291
 
                                return absPath;
292
 
                        
293
 
                        absPath           = Path.GetFullPath (absPath);
294
 
                        baseDirectoryPath = Path.GetFullPath (baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar));
295
 
                        
296
 
                        string[] bPath = baseDirectoryPath.Split (separators);
297
 
                        string[] aPath = absPath.Split (separators);
298
 
                        int indx = 0;
299
 
                        
300
 
                        for (; indx < Math.Min (bPath.Length, aPath.Length); indx++) {
301
 
                                if (!bPath[indx].Equals(aPath[indx]))
302
 
                                        break;
303
 
                        }
304
 
                        
305
 
                        if (indx == 0) 
306
 
                                return absPath;
307
 
                        
308
 
                        StringBuilder result = new StringBuilder ();
309
 
                        
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);
314
 
                        }
315
 
                        
316
 
                        
317
 
                        result.Append (String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx));
318
 
                        if (result.Length == 0)
319
 
                                return ".";
320
 
                        return result.ToString ();
321
 
                }*/
322
 
                
323
 
                static bool IsSeparator (char ch)
324
 
                {
325
 
                        return ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar;
326
 
                }
327
 
                
328
 
                public unsafe static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
329
 
                {
330
 
                        if (!Path.IsPathRooted (absPath) || string.IsNullOrEmpty (baseDirectoryPath))
331
 
                                return absPath;
332
 
                                
333
 
                        absPath = Path.GetFullPath (absPath);
334
 
                        baseDirectoryPath = Path.GetFullPath (baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar));
335
 
                        
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;
341
 
                                
342
 
                                int indx = 0;
343
 
                                // search common base path
344
 
                                var a = aPtr;
345
 
                                var b = bPtr;
346
 
                                while (a < aEnd) {
347
 
                                        if (*a != *b)
348
 
                                                break;
349
 
                                        if (IsSeparator (*a)) {
350
 
                                                indx++;
351
 
                                                lastStartA = a + 1;
352
 
                                                lastStartB = b; 
353
 
                                        }
354
 
                                        a++;
355
 
                                        b++;
356
 
                                        if (b >= bEnd) {
357
 
                                                if (a >= aEnd || IsSeparator (*a)) {
358
 
                                                        indx++;
359
 
                                                        lastStartA = a + 1;
360
 
                                                        lastStartB = b;
361
 
                                                }
362
 
                                                break;
363
 
                                        }
364
 
                                }
365
 
                                if (indx == 0) 
366
 
                                        return absPath;
367
 
                                
368
 
                                if (lastStartA >= aEnd)
369
 
                                        return ".";
370
 
 
371
 
                                // handle case a: some/path b: some/path/deeper...
372
 
                                if (a >= aEnd) {
373
 
                                        if (IsSeparator (*b)) {
374
 
                                                lastStartA = a + 1;
375
 
                                                lastStartB = b;
376
 
                                        }
377
 
                                }
378
 
                                
379
 
                                // look how many levels to go up into the base path
380
 
                                int goUpCount = 0;
381
 
                                while (lastStartB < bEnd) {
382
 
                                        if (IsSeparator (*lastStartB))
383
 
                                                goUpCount++;
384
 
                                        lastStartB++;
385
 
                                }
386
 
                                var size = goUpCount * 2 + goUpCount + aEnd - lastStartA;
387
 
                                var result = new char [size];
388
 
                                fixed (char* rPtr = result) {
389
 
                                        // go paths up
390
 
                                        var r = rPtr;
391
 
                                        for (int i = 0; i < goUpCount; i++) {
392
 
                                                *(r++) = '.';
393
 
                                                *(r++) = '.';
394
 
                                                *(r++) = Path.DirectorySeparatorChar;
395
 
                                        }
396
 
                                        // copy the remaining absulute path
397
 
                                        while (lastStartA < aEnd)
398
 
                                                *(r++) = *(lastStartA++);
399
 
                                }
400
 
                                return new string (result);
401
 
                        }
402
 
                }
403
 
                
404
 
                public static string RelativeToAbsolutePath (string baseDirectoryPath, string relPath)
405
 
                {
406
 
                        return Path.GetFullPath (Path.Combine (baseDirectoryPath, relPath));
407
 
                }
408
 
                
409
 
                public static bool IsValidPath (string fileName)
410
 
                {
411
 
                        if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty) 
412
 
                                return false;
413
 
                        if (fileName.IndexOfAny (Path.GetInvalidPathChars ()) >= 0)
414
 
                                return false;
415
 
                        return true;
416
 
                }
417
 
 
418
 
                public static bool IsValidFileName (string fileName)
419
 
                {
420
 
                        if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty) 
421
 
                                return false;
422
 
                        if (fileName.IndexOfAny (Path.GetInvalidFileNameChars ()) >= 0)
423
 
                                return false;
424
 
                        return true;
425
 
                }
426
 
                
427
 
                public static bool IsDirectory (string filename)
428
 
                {
429
 
                        return Directory.Exists (filename) && (File.GetAttributes (filename) & FileAttributes.Directory) != 0;
430
 
                }
431
 
                
432
 
                public static string GetFullPath (string path)
433
 
                {
434
 
                        if (path == null)
435
 
                                throw new ArgumentNullException ("path");
436
 
                        // Note: It's not required for Path.GetFullPath (path) that path exists.
437
 
                        return Path.GetFullPath (path); 
438
 
                }
439
 
                
440
 
                public static string CreateTempDirectory ()
441
 
                {
442
 
                        Random rnd = new Random ();
443
 
                        string result;
444
 
                        while (true) {
445
 
                                result = Path.Combine (Path.GetTempPath (), "mdTmpDir" + rnd.Next ());
446
 
                                if (!Directory.Exists (result))
447
 
                                        break;
448
 
                        } 
449
 
                        Directory.CreateDirectory (result);
450
 
                        return result;
451
 
                }
452
 
 
453
 
                public static string NormalizeRelativePath (string path)
454
 
                {
455
 
                        string result = path.Trim (Path.DirectorySeparatorChar, ' ');
456
 
                        while (result.StartsWith ("." + Path.DirectorySeparatorChar)) {
457
 
                                result = result.Substring (2);
458
 
                                result = result.Trim (Path.DirectorySeparatorChar);
459
 
                        }
460
 
                        return result == "." ? "" : result;
 
1
//
 
2
// FileService.cs
 
3
//
 
4
// Author:
 
5
//   Mike KrĆ¼ger <mkrueger@novell.com>
 
6
//   Lluis Sanchez Gual <lluis@novell.com>
 
7
//
 
8
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
 
9
//
 
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:
 
17
// 
 
18
// The above copyright notice and this permission notice shall be
 
19
// included in all copies or substantial portions of the Software.
 
20
// 
 
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.
 
28
//
 
29
 
 
30
using System;
 
31
using System.Diagnostics;
 
32
using System.IO;
 
33
using System.Text;
 
34
 
 
35
using Mono.Addins;
 
36
using Mono.Unix.Native;
 
37
using MonoDevelop.Core.FileSystem;
 
38
using System.Collections.Generic;
 
39
using System.Threading;
 
40
using System.Linq;
 
41
using System.Threading.Tasks;
 
42
using System.Net;
 
43
 
 
44
namespace MonoDevelop.Core
 
45
{
 
46
        public static class FileService
 
47
        {
 
48
                const string addinFileSystemExtensionPath = "/MonoDevelop/Core/FileSystemExtensions";
 
49
 
 
50
                static FileServiceErrorHandler errorHandler;
 
51
                
 
52
                static FileSystemExtension fileSystemChain;
 
53
                static FileSystemExtension defaultExtension = Platform.IsWindows ? new DefaultFileSystemExtension () : new UnixFileSystemExtension () ;
 
54
                
 
55
                static EventQueue eventQueue = new EventQueue ();
 
56
                
 
57
                static string applicationRootPath = Path.Combine (PropertyService.EntryAssemblyPath, "..");
 
58
                public static string ApplicationRootPath {
 
59
                        get {
 
60
                                return applicationRootPath;
 
61
                        }
 
62
                }
 
63
                
 
64
                static FileService()
 
65
                {
 
66
                        AddinManager.ExtensionChanged += delegate (object sender, ExtensionEventArgs args) {
 
67
                                if (args.PathChanged (addinFileSystemExtensionPath))
 
68
                                        UpdateExtensions ();
 
69
                        };
 
70
                        UpdateExtensions ();
 
71
                }
 
72
                
 
73
                static void UpdateExtensions ()
 
74
                {
 
75
                        if (!Runtime.Initialized) {
 
76
                                fileSystemChain = defaultExtension;
 
77
                                return;
 
78
                        }
 
79
                        
 
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];
 
83
                        }
 
84
 
 
85
                        if (extensions.Length > 0) {
 
86
                                extensions [extensions.Length - 1].Next = defaultExtension;
 
87
                                fileSystemChain = extensions [0];
 
88
                        } elseĀ {
 
89
                                fileSystemChain = defaultExtension;
 
90
                        }
 
91
                }
 
92
                
 
93
                public static FilePath ResolveFullPath (FilePath path)
 
94
                {
 
95
                        try {
 
96
                                return GetFileSystemForPath (path, false).ResolveFullPath (path);
 
97
                        } catch (Exception e) {
 
98
                                if (!HandleError (GettextCatalog.GetString ("Can't resolve full path {0}", path), e))
 
99
                                        throw;
 
100
                                return FilePath.Empty;
 
101
                        }
 
102
                }
 
103
                
 
104
                public static void DeleteFile (string fileName)
 
105
                {
 
106
                        Debug.Assert (!String.IsNullOrEmpty (fileName));
 
107
                        try {
 
108
                                GetFileSystemForPath (fileName, false).DeleteFile (fileName);
 
109
                        } catch (Exception e) {
 
110
                                if (!HandleError (GettextCatalog.GetString ("Can't remove file {0}", fileName), e))
 
111
                                        throw;
 
112
                                return;
 
113
                        }
 
114
                        OnFileRemoved (new FileEventArgs (fileName, false));
 
115
                }
 
116
                
 
117
                public static void DeleteDirectory (string path)
 
118
                {
 
119
                        Debug.Assert (!String.IsNullOrEmpty (path));
 
120
                        try {
 
121
                                GetFileSystemForPath (path, true).DeleteDirectory (path);
 
122
                        } catch (Exception e) {
 
123
                                if (!HandleError (GettextCatalog.GetString ("Can't remove directory {0}", path), e))
 
124
                                        throw;
 
125
                                return;
 
126
                        }
 
127
                        OnFileRemoved (new FileEventArgs (path, true));
 
128
                }
 
129
                
 
130
                public static void RenameFile (string oldName, string newName)
 
131
                {
 
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));
 
138
                        }
 
139
                }
 
140
                
 
141
                public static void RenameDirectory (string oldName, string newName)
 
142
                {
 
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));
 
151
                        }
 
152
                }
 
153
                
 
154
                public static void CopyFile (string srcFile, string dstFile)
 
155
                {
 
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));
 
160
                }
 
161
 
 
162
                public static void MoveFile (string srcFile, string dstFile)
 
163
                {
 
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));
 
169
                }
 
170
                
 
171
                static void InternalMoveFile (string srcFile, string dstFile)
 
172
                {
 
173
                        Debug.Assert (!String.IsNullOrEmpty (srcFile));
 
174
                        Debug.Assert (!String.IsNullOrEmpty (dstFile));
 
175
                        FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
 
176
                        FileSystemExtension dstExt = GetFileSystemForPath (dstFile, false);
 
177
                        
 
178
                        if (srcExt == dstExt) {
 
179
                                // Everything can be handled by the same file system
 
180
                                srcExt.MoveFile (srcFile, dstFile);
 
181
                        } else {
 
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);
 
187
                        }
 
188
                }
 
189
                
 
190
                static void InternalRenameFile (string srcFile, string newName)
 
191
                {
 
192
                        Debug.Assert (!string.IsNullOrEmpty (srcFile));
 
193
                        Debug.Assert (!string.IsNullOrEmpty (newName));
 
194
                        FileSystemExtension srcExt = GetFileSystemForPath (srcFile, false);
 
195
                        
 
196
                        srcExt.RenameFile (srcFile, newName);
 
197
                }
 
198
                
 
199
                public static void CreateDirectory (string path)
 
200
                {
 
201
                        if (!Directory.Exists (path)) {
 
202
                                Debug.Assert (!String.IsNullOrEmpty (path));
 
203
                                GetFileSystemForPath (path, true).CreateDirectory (path);
 
204
                                OnFileCreated (new FileEventArgs (path, true));
 
205
                        }
 
206
                }
 
207
                
 
208
                public static void CopyDirectory (string srcPath, string dstPath)
 
209
                {
 
210
                        Debug.Assert (!String.IsNullOrEmpty (srcPath));
 
211
                        Debug.Assert (!String.IsNullOrEmpty (dstPath));
 
212
                        GetFileSystemForPath (dstPath, true).CopyDirectory (srcPath, dstPath);
 
213
                }
 
214
                
 
215
                public static void MoveDirectory (string srcPath, string dstPath)
 
216
                {
 
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));
 
222
                }
 
223
                
 
224
                static void InternalMoveDirectory (string srcPath, string dstPath)
 
225
                {
 
226
                        Debug.Assert (!String.IsNullOrEmpty (srcPath));
 
227
                        Debug.Assert (!String.IsNullOrEmpty (dstPath));
 
228
                        FileSystemExtension srcExt = GetFileSystemForPath (srcPath, true);
 
229
                        FileSystemExtension dstExt = GetFileSystemForPath (dstPath, true);
 
230
                        
 
231
                        if (srcExt == dstExt) {
 
232
                                // Everything can be handled by the same file system
 
233
                                srcExt.MoveDirectory (srcPath, dstPath);
 
234
                        } else {
 
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);
 
240
                        }
 
241
                }
 
242
                
 
243
                public static bool RequestFileEdit (string fileName)
 
244
                {
 
245
                        Debug.Assert (!String.IsNullOrEmpty (fileName));
 
246
                        return GetFileSystemForPath (fileName, false).RequestFileEdit (fileName);
 
247
                }
 
248
                
 
249
                public static void NotifyFileChanged (FilePath fileName)
 
250
                {
 
251
                        NotifyFilesChanged (new FilePath[] { fileName });
 
252
                }
 
253
                
 
254
                public static void NotifyFilesChanged (IEnumerable<FilePath> files)
 
255
                {
 
256
                        try {
 
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);
 
262
                        }
 
263
                }
 
264
                
 
265
                public static void NotifyFileRemoved (string fileName)
 
266
                {
 
267
                        NotifyFilesRemoved (new FilePath[] { fileName });
 
268
                }
 
269
                
 
270
                public static void NotifyFilesRemoved (IEnumerable<FilePath> files)
 
271
                {
 
272
                        try {
 
273
                                OnFileRemoved (new FileEventArgs (files, false));
 
274
                        } catch (Exception ex) {
 
275
                                LoggingService.LogError ("File remove notification failed", ex);
 
276
                        }
 
277
                }
 
278
                
 
279
                internal static FileSystemExtension GetFileSystemForPath (string path, bool isDirectory)
 
280
                {
 
281
                        Debug.Assert (!String.IsNullOrEmpty (path));
 
282
                        FileSystemExtension nx = fileSystemChain;
 
283
                        while (nx != null && !nx.CanHandlePath (path, isDirectory))
 
284
                                nx = nx.Next;
 
285
                        return nx;
 
286
                }
 
287
                
 
288
/*
 
289
                readonly static char[] separators = { Path.DirectorySeparatorChar, Path.VolumeSeparatorChar, Path.AltDirectorySeparatorChar };
 
290
                public static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
 
291
                {
 
292
                        if (!Path.IsPathRooted (absPath))
 
293
                                return absPath;
 
294
                        
 
295
                        absPath           = Path.GetFullPath (absPath);
 
296
                        baseDirectoryPath = Path.GetFullPath (baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar));
 
297
                        
 
298
                        string[] bPath = baseDirectoryPath.Split (separators);
 
299
                        string[] aPath = absPath.Split (separators);
 
300
                        int indx = 0;
 
301
                        
 
302
                        for (; indx < Math.Min (bPath.Length, aPath.Length); indx++) {
 
303
                                if (!bPath[indx].Equals(aPath[indx]))
 
304
                                        break;
 
305
                        }
 
306
                        
 
307
                        if (indx == 0) 
 
308
                                return absPath;
 
309
                        
 
310
                        StringBuilder result = new StringBuilder ();
 
311
                        
 
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);
 
316
                        }
 
317
                        
 
318
                        
 
319
                        result.Append (String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx));
 
320
                        if (result.Length == 0)
 
321
                                return ".";
 
322
                        return result.ToString ();
 
323
                }*/
 
324
                
 
325
                static bool IsSeparator (char ch)
 
326
                {
 
327
                        return ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar;
 
328
                }
 
329
                
 
330
                public unsafe static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
 
331
                {
 
332
                        if (!Path.IsPathRooted (absPath) || string.IsNullOrEmpty (baseDirectoryPath))
 
333
                                return absPath;
 
334
                                
 
335
                        absPath = Path.GetFullPath (absPath);
 
336
                        baseDirectoryPath = Path.GetFullPath (baseDirectoryPath).TrimEnd (Path.DirectorySeparatorChar);
 
337
                        
 
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;
 
343
                                
 
344
                                int indx = 0;
 
345
                                // search common base path
 
346
                                var a = aPtr;
 
347
                                var b = bPtr;
 
348
                                while (a < aEnd) {
 
349
                                        if (*a != *b)
 
350
                                                break;
 
351
                                        if (IsSeparator (*a)) {
 
352
                                                indx++;
 
353
                                                lastStartA = a + 1;
 
354
                                                lastStartB = b; 
 
355
                                        }
 
356
                                        a++;
 
357
                                        b++;
 
358
                                        if (b >= bEnd) {
 
359
                                                if (a >= aEnd || IsSeparator (*a)) {
 
360
                                                        indx++;
 
361
                                                        lastStartA = a + 1;
 
362
                                                        lastStartB = b;
 
363
                                                }
 
364
                                                break;
 
365
                                        }
 
366
                                }
 
367
                                if (indx == 0) 
 
368
                                        return absPath;
 
369
                                
 
370
                                if (lastStartA >= aEnd)
 
371
                                        return ".";
 
372
 
 
373
                                // handle case a: some/path b: some/path/deeper...
 
374
                                if (a >= aEnd) {
 
375
                                        if (IsSeparator (*b)) {
 
376
                                                lastStartA = a + 1;
 
377
                                                lastStartB = b;
 
378
                                        }
 
379
                                }
 
380
                                
 
381
                                // look how many levels to go up into the base path
 
382
                                int goUpCount = 0;
 
383
                                while (lastStartB < bEnd) {
 
384
                                        if (IsSeparator (*lastStartB))
 
385
                                                goUpCount++;
 
386
                                        lastStartB++;
 
387
                                }
 
388
                                var size = goUpCount * 2 + goUpCount + aEnd - lastStartA;
 
389
                                var result = new char [size];
 
390
                                fixed (char* rPtr = result) {
 
391
                                        // go paths up
 
392
                                        var r = rPtr;
 
393
                                        for (int i = 0; i < goUpCount; i++) {
 
394
                                                *(r++) = '.';
 
395
                                                *(r++) = '.';
 
396
                                                *(r++) = Path.DirectorySeparatorChar;
 
397
                                        }
 
398
                                        // copy the remaining absulute path
 
399
                                        while (lastStartA < aEnd)
 
400
                                                *(r++) = *(lastStartA++);
 
401
                                }
 
402
                                return new string (result);
 
403
                        }
 
404
                }
 
405
                
 
406
                public static string RelativeToAbsolutePath (string baseDirectoryPath, string relPath)
 
407
                {
 
408
                        return Path.GetFullPath (Path.Combine (baseDirectoryPath, relPath));
 
409
                }
 
410
                
 
411
                public static bool IsValidPath (string fileName)
 
412
                {
 
413
                        if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty) 
 
414
                                return false;
 
415
                        if (fileName.IndexOfAny (Path.GetInvalidPathChars ()) >= 0)
 
416
                                return false;
 
417
                        return true;
 
418
                }
 
419
 
 
420
                public static bool IsValidFileName (string fileName)
 
421
                {
 
422
                        if (String.IsNullOrEmpty (fileName) || fileName.Trim() == string.Empty) 
 
423
                                return false;
 
424
                        if (fileName.IndexOfAny (Path.GetInvalidFileNameChars ()) >= 0)
 
425
                                return false;
 
426
                        return true;
 
427
                }
 
428
                
 
429
                public static bool IsDirectory (string filename)
 
430
                {
 
431
                        return Directory.Exists (filename) && (File.GetAttributes (filename) & FileAttributes.Directory) != 0;
 
432
                }
 
433
                
 
434
                public static string GetFullPath (string path)
 
435
                {
 
436
                        if (path == null)
 
437
                                throw new ArgumentNullException ("path");
 
438
                        // Note: It's not required for Path.GetFullPath (path) that path exists.
 
439
                        return Path.GetFullPath (path); 
 
440
                }
 
441
                
 
442
                public static string CreateTempDirectory ()
 
443
                {
 
444
                        Random rnd = new Random ();
 
445
                        string result;
 
446
                        while (true) {
 
447
                                result = Path.Combine (Path.GetTempPath (), "mdTmpDir" + rnd.Next ());
 
448
                                if (!Directory.Exists (result))
 
449
                                        break;
 
450
                        } 
 
451
                        Directory.CreateDirectory (result);
 
452
                        return result;
 
453
                }
 
454
 
 
455
                public static string NormalizeRelativePath (string path)
 
456
                {
 
457
                        string result = path.Trim (Path.DirectorySeparatorChar, ' ');
 
458
                        while (result.StartsWith ("." + Path.DirectorySeparatorChar)) {
 
459
                                result = result.Substring (2);
 
460
                                result = result.Trim (Path.DirectorySeparatorChar);
 
461
                        }
 
462
                        return result == "." ? "" : result;
461
463
                }
462
464
 
463
465
                // Atomic rename of a file. It does not fire events.
464
466
                public static void SystemRename (string sourceFile, string destFile)
465
 
                {
 
467
                {
 
468
                        if (string.IsNullOrEmpty (sourceFile))
 
469
                                throw new ArgumentException ("sourceFile");
 
470
 
 
471
                        if (string.IsNullOrEmpty (destFile))
 
472
                                throw new ArgumentException ("destFile");
 
473
 
466
474
                        //FIXME: use the atomic System.IO.File.Replace on NTFS
467
475
                        if (Platform.IsWindows) {
468
476
                                string wtmp = null;
496
504
                                }
497
505
                        }
498
506
                        else {
499
 
                                Mono.Unix.Native.Syscall.rename (sourceFile, destFile);
500
 
                        }
501
 
                }
502
 
                
503
 
                /// <summary>
504
 
                /// Removes the directory if it's empty.
505
 
                /// </summary>
506
 
                public static void RemoveDirectoryIfEmpty (string directory)
507
 
                {
508
 
                        if (Directory.Exists (directory) && !Directory.EnumerateFiles (directory).Any ())
509
 
                                Directory.Delete (directory);
510
 
                }
511
 
                
512
 
                /// <summary>
513
 
                /// Creates a directory if it does not already exist.
514
 
                /// </summary>
515
 
                public static void EnsureDirectoryExists (string directory)
516
 
                {
517
 
                        if (!Directory.Exists (directory))
518
 
                                Directory.CreateDirectory (directory);
519
 
                }
520
 
                
521
 
                /// <summary>
522
 
                /// Makes the path separators native.
523
 
                /// </summary>
524
 
                public static string MakePathSeparatorsNative (string path)
525
 
                {
526
 
                        if (path == null || path.Length == 0)
527
 
                                return path;
528
 
                        char c = Path.DirectorySeparatorChar == '\\'? '/' : '\\'; 
529
 
                        return path.Replace (c, Path.DirectorySeparatorChar);
530
 
                }
531
 
 
532
 
                static bool HandleError (string message, Exception ex)
533
 
                {
534
 
                        return errorHandler != null ? errorHandler (message, ex) : false;
535
 
                }
536
 
                
537
 
                public static FileServiceErrorHandler ErrorHandler {
538
 
                        get { return errorHandler; }
539
 
                        set { errorHandler = value; }
540
 
                }
541
 
                
542
 
                public static void FreezeEvents ()
543
 
                {
544
 
                        eventQueue.Freeze ();
545
 
                }
546
 
                
547
 
                public static void ThawEvents ()
548
 
                {
549
 
                        eventQueue.Thaw ();
550
 
                }
551
 
 
552
 
                public static event EventHandler<FileEventArgs> FileCreated;
553
 
                static void OnFileCreated (FileEventArgs args)
554
 
                {
555
 
                        foreach (FileEventInfo fi in args) {
556
 
                                if (fi.IsDirectory)
557
 
                                        Counters.DirectoriesCreated++;
558
 
                                else
559
 
                                        Counters.FilesCreated++;
560
 
                        }
561
 
 
562
 
                        eventQueue.RaiseEvent (() => FileCreated, args);
563
 
                }
564
 
                
565
 
                public static event EventHandler<FileCopyEventArgs> FileRenamed;
566
 
                static void OnFileRenamed (FileCopyEventArgs args)
567
 
                {
568
 
                        foreach (FileCopyEventInfo fi in args) {
569
 
                                if (fi.IsDirectory)
570
 
                                        Counters.DirectoriesRenamed++;
571
 
                                else
572
 
                                        Counters.FilesRenamed++;
573
 
                        }
574
 
                        
575
 
                        eventQueue.RaiseEvent (() => FileRenamed, args);
576
 
                }
577
 
                
578
 
                public static event EventHandler<FileEventArgs> FileRemoved;
579
 
                static void OnFileRemoved (FileEventArgs args)
580
 
                {
581
 
                        foreach (FileEventInfo fi in args) {
582
 
                                if (fi.IsDirectory)
583
 
                                        Counters.DirectoriesRemoved++;
584
 
                                else
585
 
                                        Counters.FilesRemoved++;
586
 
                        }
587
 
                        
588
 
                        eventQueue.RaiseEvent (() => FileRemoved, args);
589
 
                }
590
 
                
591
 
                public static event EventHandler<FileEventArgs> FileChanged;
592
 
                static void OnFileChanged (FileEventArgs args)
593
 
                {
594
 
                        Counters.FileChangeNotifications++;
595
 
                        eventQueue.RaiseEvent (() => FileChanged, null, args);
596
 
                }
597
 
        }
598
 
        
599
 
        class EventQueue
600
 
        {
601
 
                class EventData
602
 
                {
603
 
                        public Func<Delegate> Delegate;
604
 
                        public object ThisObject;
605
 
                        public EventArgs Args;
606
 
                }
607
 
                
608
 
                List<EventData> events = new List<EventData> ();
609
 
                
610
 
                int frozen;
611
 
                object defaultSourceObject;
612
 
                
613
 
                public EventQueue ()
614
 
                {
615
 
                }
616
 
                
617
 
                public EventQueue (object defaultSourceObject)
618
 
                {
619
 
                        this.defaultSourceObject = defaultSourceObject;
620
 
                }
621
 
                
622
 
                public void Freeze ()
623
 
                {
624
 
                        lock (events) {
625
 
                                frozen++;
626
 
                        }
627
 
                }
628
 
                
629
 
                public void Thaw ()
630
 
                {
631
 
                        List<EventData> pendingEvents = null;
632
 
                        lock (events) {
633
 
                                if (--frozen == 0) {
634
 
                                        pendingEvents = events;
635
 
                                        events = new List<EventData> ();
636
 
                                }
637
 
                        }
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);
646
 
                                                        continue;
647
 
                                                }
648
 
                                        }
649
 
                                        if (del != null)
650
 
                                                del.DynamicInvoke (ev.ThisObject, ev.Args);
651
 
                                }
652
 
                        }
653
 
                }
654
 
                
655
 
                public void RaiseEvent (Func<Delegate> d, EventArgs args)
656
 
                {
657
 
                        RaiseEvent (d, defaultSourceObject, args);
658
 
                }
659
 
                
660
 
                public void RaiseEvent (Func<Delegate> d, object thisObj, EventArgs args)
661
 
                {
662
 
                        Delegate del = d ();
663
 
                        lock (events) {
664
 
                                if (frozen > 0) {
665
 
                                        EventData ed = new EventData ();
666
 
                                        ed.Delegate = d;
667
 
                                        ed.ThisObject = thisObj;
668
 
                                        ed.Args = args;
669
 
                                        events.Add (ed);
670
 
                                        return;
671
 
                                }
672
 
                        }
673
 
                        if (del != null)
674
 
                                del.DynamicInvoke (thisObj, args);
675
 
                }
676
 
        }
677
 
        
678
 
        public delegate bool FileServiceErrorHandler (string message, Exception ex);
679
 
}
 
507
                                if (Syscall.rename (sourceFile, destFile) != 0) {
 
508
                                        switch (Stdlib.GetLastError ()) {
 
509
                                        case Errno.EACCES:
 
510
                                        case Errno.EPERM:
 
511
                                                throw new UnauthorizedAccessException ();
 
512
                                        case Errno.EINVAL:
 
513
                                                throw new InvalidOperationException ();
 
514
                                        case Errno.ENOTDIR:
 
515
                                                throw new DirectoryNotFoundException ();
 
516
                                        case Errno.ENOENT:
 
517
                                                throw new FileNotFoundException ();
 
518
                                        case Errno.ENAMETOOLONG:
 
519
                                                throw new PathTooLongException ();
 
520
                                        default:
 
521
                                                throw new IOException ();
 
522
                                        }
 
523
                                }
 
524
                        }
 
525
                }
 
526
                
 
527
                /// <summary>
 
528
                /// Removes the directory if it's empty.
 
529
                /// </summary>
 
530
                public static void RemoveDirectoryIfEmpty (string directory)
 
531
                {
 
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);
 
536
                }
 
537
                
 
538
                /// <summary>
 
539
                /// Creates a directory if it does not already exist.
 
540
                /// </summary>
 
541
                public static void EnsureDirectoryExists (string directory)
 
542
                {
 
543
                        if (!Directory.Exists (directory))
 
544
                                Directory.CreateDirectory (directory);
 
545
                }
 
546
                
 
547
                /// <summary>
 
548
                /// Makes the path separators native.
 
549
                /// </summary>
 
550
                public static string MakePathSeparatorsNative (string path)
 
551
                {
 
552
                        if (path == null || path.Length == 0)
 
553
                                return path;
 
554
                        char c = Path.DirectorySeparatorChar == '\\'? '/' : '\\'; 
 
555
                        return path.Replace (c, Path.DirectorySeparatorChar);
 
556
                }
 
557
 
 
558
                static bool HandleError (string message, Exception ex)
 
559
                {
 
560
                        return errorHandler != null ? errorHandler (message, ex) : false;
 
561
                }
 
562
                
 
563
                public static FileServiceErrorHandler ErrorHandler {
 
564
                        get { return errorHandler; }
 
565
                        set { errorHandler = value; }
 
566
                }
 
567
                
 
568
                public static void FreezeEvents ()
 
569
                {
 
570
                        eventQueue.Freeze ();
 
571
                }
 
572
                
 
573
                public static void ThawEvents ()
 
574
                {
 
575
                        eventQueue.Thaw ();
 
576
                }
 
577
 
 
578
                public static event EventHandler<FileEventArgs> FileCreated;
 
579
                static void OnFileCreated (FileEventArgs args)
 
580
                {
 
581
                        foreach (FileEventInfo fi in args) {
 
582
                                if (fi.IsDirectory)
 
583
                                        Counters.DirectoriesCreated++;
 
584
                                else
 
585
                                        Counters.FilesCreated++;
 
586
                        }
 
587
 
 
588
                        eventQueue.RaiseEvent (() => FileCreated, args);
 
589
                }
 
590
                
 
591
                public static event EventHandler<FileCopyEventArgs> FileRenamed;
 
592
                static void OnFileRenamed (FileCopyEventArgs args)
 
593
                {
 
594
                        foreach (FileCopyEventInfo fi in args) {
 
595
                                if (fi.IsDirectory)
 
596
                                        Counters.DirectoriesRenamed++;
 
597
                                else
 
598
                                        Counters.FilesRenamed++;
 
599
                        }
 
600
                        
 
601
                        eventQueue.RaiseEvent (() => FileRenamed, args);
 
602
                }
 
603
                
 
604
                public static event EventHandler<FileEventArgs> FileRemoved;
 
605
                static void OnFileRemoved (FileEventArgs args)
 
606
                {
 
607
                        foreach (FileEventInfo fi in args) {
 
608
                                if (fi.IsDirectory)
 
609
                                        Counters.DirectoriesRemoved++;
 
610
                                else
 
611
                                        Counters.FilesRemoved++;
 
612
                        }
 
613
                        
 
614
                        eventQueue.RaiseEvent (() => FileRemoved, args);
 
615
                }
 
616
                
 
617
                public static event EventHandler<FileEventArgs> FileChanged;
 
618
                static void OnFileChanged (FileEventArgs args)
 
619
                {
 
620
                        Counters.FileChangeNotifications++;
 
621
                        eventQueue.RaiseEvent (() => FileChanged, null, args);
 
622
                }
 
623
 
 
624
                public static Task<bool> UpdateDownloadedCacheFile (string url, string cacheFile,
 
625
                        Func<Stream,bool> validateDownload = null, CancellationToken ct = new CancellationToken ())
 
626
                {
 
627
                        var tcs = new TaskCompletionSource<bool> ();
 
628
 
 
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);
 
633
 
 
634
                                try {
 
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;
 
639
 
 
640
                                        request.BeginGetResponse (HandleResponse, new CacheFileDownload {
 
641
                                                Request = request,
 
642
                                                Url = url,
 
643
                                                CacheFile = cacheFile,
 
644
                                                ValidateDownload = validateDownload,
 
645
                                                CancellationToken = ct,
 
646
                                                Tcs = tcs,
 
647
                                        });
 
648
                                } catch (Exception ex) {
 
649
                                        tcs.SetException (ex);
 
650
                                }
 
651
                        });
 
652
 
 
653
                        return tcs.Task;
 
654
                }
 
655
 
 
656
                class CacheFileDownload
 
657
                {
 
658
                        public HttpWebRequest Request;
 
659
                        public string CacheFile, Url;
 
660
                        public Func<Stream,bool> ValidateDownload;
 
661
                        public CancellationToken CancellationToken;
 
662
                        public TaskCompletionSource<bool> Tcs;
 
663
                }
 
664
 
 
665
                static void HandleResponse (IAsyncResult ar)
 
666
                {
 
667
                        var c = (CacheFileDownload) ar.AsyncState;
 
668
                        bool deleteTempFile = true;
 
669
                        var tempFile = c.CacheFile + ".temp";
 
670
 
 
671
                        try {
 
672
                                if (c.CancellationToken.IsCancellationRequested)
 
673
                                        c.Tcs.TrySetCanceled ();
 
674
 
 
675
                                try {
 
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);
 
681
                                        }
 
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);
 
687
                                                        return;
 
688
                                                }
 
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));
 
692
                                                        return;
 
693
                                                }
 
694
                                        }
 
695
                                        throw;
 
696
                                }
 
697
 
 
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 ();
 
702
 
 
703
                                        using (var f = File.OpenRead (tempFile)) {
 
704
                                                try {
 
705
                                                        if (!c.ValidateDownload (f)) {
 
706
                                                                c.Tcs.TrySetException (new Exception ("Failed to validate downloaded file"));
 
707
                                                                return;
 
708
                                                        }
 
709
                                                } catch (Exception ex) {
 
710
                                                        c.Tcs.TrySetException (new Exception ("Failed to validate downloaded file", ex));
 
711
                                                }
 
712
                                        }
 
713
                                }
 
714
 
 
715
                                if (c.CancellationToken.IsCancellationRequested)
 
716
                                        c.Tcs.TrySetCanceled ();
 
717
 
 
718
                                SystemRename (tempFile, c.CacheFile);
 
719
                                deleteTempFile = false;
 
720
                                c.Tcs.TrySetResult (true);
 
721
                        } catch (Exception ex) {
 
722
                                c.Tcs.TrySetException (ex);
 
723
                        } finally {
 
724
                                if (deleteTempFile) {
 
725
                                        try {
 
726
                                                File.Delete (tempFile);
 
727
                                        } catch {}
 
728
                                }
 
729
                        }
 
730
                }
 
731
        }
 
732
        
 
733
        class EventQueue
 
734
        {
 
735
                class EventData
 
736
                {
 
737
                        public Func<Delegate> Delegate;
 
738
                        public object ThisObject;
 
739
                        public EventArgs Args;
 
740
                }
 
741
                
 
742
                List<EventData> events = new List<EventData> ();
 
743
                
 
744
                int frozen;
 
745
                object defaultSourceObject;
 
746
                
 
747
                public EventQueue ()
 
748
                {
 
749
                }
 
750
                
 
751
                public EventQueue (object defaultSourceObject)
 
752
                {
 
753
                        this.defaultSourceObject = defaultSourceObject;
 
754
                }
 
755
                
 
756
                public void Freeze ()
 
757
                {
 
758
                        lock (events) {
 
759
                                frozen++;
 
760
                        }
 
761
                }
 
762
                
 
763
                public void Thaw ()
 
764
                {
 
765
                        List<EventData> pendingEvents = null;
 
766
                        lock (events) {
 
767
                                if (--frozen == 0) {
 
768
                                        pendingEvents = events;
 
769
                                        events = new List<EventData> ();
 
770
                                }
 
771
                        }
 
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);
 
780
                                                        continue;
 
781
                                                }
 
782
                                        }
 
783
                                        if (del != null)
 
784
                                                del.DynamicInvoke (ev.ThisObject, ev.Args);
 
785
                                }
 
786
                        }
 
787
                }
 
788
                
 
789
                public void RaiseEvent (Func<Delegate> d, EventArgs args)
 
790
                {
 
791
                        RaiseEvent (d, defaultSourceObject, args);
 
792
                }
 
793
                
 
794
                public void RaiseEvent (Func<Delegate> d, object thisObj, EventArgs args)
 
795
                {
 
796
                        Delegate del = d ();
 
797
                        lock (events) {
 
798
                                if (frozen > 0) {
 
799
                                        EventData ed = new EventData ();
 
800
                                        ed.Delegate = d;
 
801
                                        ed.ThisObject = thisObj;
 
802
                                        ed.Args = args;
 
803
                                        events.Add (ed);
 
804
                                        return;
 
805
                                }
 
806
                        }
 
807
                        if (del != null)
 
808
                                del.DynamicInvoke (thisObj, args);
 
809
                }
 
810
        }
 
811
        
 
812
        public delegate bool FileServiceErrorHandler (string message, Exception ex);
 
813
}