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

« back to all changes in this revision

Viewing changes to src/addins/MonoDevelop.MacDev/XcodeSyncing/XcodeProjectTracker.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
 
// XcodeProjectTracker.cs
3
 
//  
4
 
// Author:
5
 
//       Michael Hutchinson <mhutchinson@novell.com>
6
 
// 
7
 
// Copyright (c) 2011 Novell, Inc.
8
 
// 
9
 
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
 
// of this software and associated documentation files (the "Software"), to deal
11
 
// in the Software without restriction, including without limitation the rights
12
 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
 
// copies of the Software, and to permit persons to whom the Software is
14
 
// furnished to do so, subject to the following conditions:
15
 
// 
16
 
// The above copyright notice and this permission notice shall be included in
17
 
// all copies or substantial portions of the Software.
18
 
// 
19
 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
 
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
 
// THE SOFTWARE.
26
 
 
27
 
using System;
28
 
using System.IO;
29
 
using System.Xml;
30
 
using System.Linq;
31
 
using System.Diagnostics;
32
 
using System.Collections.Generic;
33
 
 
34
 
using MonoDevelop.Core;
35
 
using MonoDevelop.Projects;
36
 
using MonoDevelop.MacDev.ObjCIntegration;
37
 
using System.Threading.Tasks;
38
 
using MonoDevelop.MacDev.XcodeIntegration;
39
 
using MonoDevelop.MacInterop;
40
 
 
41
 
namespace MonoDevelop.MacDev.XcodeSyncing
42
 
{
43
 
        public interface IXcodeTrackedProject
44
 
        {
45
 
                XcodeProjectTracker XcodeProjectTracker { get; }
46
 
        }
47
 
        
48
 
        public abstract class XcodeProjectTracker : IDisposable
49
 
        {
50
 
                readonly NSObjectInfoService infoService;
51
 
                readonly DotNetProject dnp;
52
 
 
53
 
                object xcode_lock = new object ();
54
 
                List<NSObjectTypeInfo> userTypes;
55
 
                XcodeMonitor xcode;
56
 
 
57
 
                bool updatingProjectFiles;
58
 
                bool disposed;
59
 
                
60
 
                FilePath ObjDir {
61
 
                        get { return dnp.BaseDirectory.Combine ("obj"); }
62
 
                }
63
 
                
64
 
                FilePath XcodeDir {
65
 
                        get { return ObjDir.Combine ("Xcode"); }
66
 
                }
67
 
 
68
 
                public XcodeProjectTracker (DotNetProject dnp, NSObjectInfoService infoService)
69
 
                {
70
 
                        if (dnp == null)
71
 
                                throw new ArgumentNullException ("dnp");
72
 
                        this.dnp = dnp;
73
 
                        this.infoService = infoService;
74
 
                        AppleSdkSettings.Changed += DisableSyncing;
75
 
                }
76
 
 
77
 
                public bool ShouldOpenInXcode (FilePath fileName)
78
 
                {
79
 
                        if (!HasInterfaceDefinitionExtension (fileName))
80
 
                                return false;
81
 
 
82
 
                        var file = dnp.Files.GetFile (fileName);
83
 
                        return file != null && (file.BuildAction == BuildAction.InterfaceDefinition);
84
 
                }
85
 
                
86
 
                public virtual bool HasInterfaceDefinitionExtension (FilePath fileName)
87
 
                {
88
 
                        return fileName.HasExtension (".xib");
89
 
                }
90
 
                
91
 
                protected virtual string[] GetFrameworks ()
92
 
                {
93
 
                        return new string[] { "Foundation" };
94
 
                }
95
 
                
96
 
                bool SyncingEnabled {
97
 
                        get { return xcode != null; }
98
 
                }
99
 
                
100
 
                void EnableSyncing (IProgressMonitor monitor)
101
 
                {
102
 
                        if (SyncingEnabled)
103
 
                                return;
104
 
                        
105
 
                        monitor.Log.WriteLine ("Enabled syncing for project: {0}", dnp.Name);
106
 
                        
107
 
                        xcode = new XcodeMonitor (XcodeDir, dnp.Name);
108
 
                        
109
 
                        dnp.FileAddedToProject += FileAddedToProject;
110
 
                        dnp.FilePropertyChangedInProject += FilePropertyChangedInProject;
111
 
                        dnp.FileRemovedFromProject += FileRemovedFromProject;
112
 
                        dnp.FileChangedInProject += FileChangedInProject;
113
 
                        dnp.NameChanged += ProjectNameChanged;
114
 
                        MonoDevelop.Ide.IdeApp.CommandService.ApplicationFocusIn += AppRegainedFocus;
115
 
                }
116
 
 
117
 
                void DisableSyncing ()
118
 
                {
119
 
                        DisableSyncing (true);
120
 
                }
121
 
                
122
 
                void DisableSyncing (bool closeProject)
123
 
                {
124
 
                        if (!SyncingEnabled)
125
 
                                return;
126
 
                        
127
 
                        XC4Debug.Log ("Disabled syncing for project: {0}", dnp.Name);
128
 
                        
129
 
                        XC4Debug.Indent ();
130
 
                        try {
131
 
                                if (closeProject)
132
 
                                        xcode.CloseProject ();
133
 
                                xcode.DeleteProjectDirectory ();
134
 
                        } finally {
135
 
                                XC4Debug.Unindent ();
136
 
                                xcode = null;
137
 
                        }
138
 
                        
139
 
                        dnp.FileAddedToProject -= FileAddedToProject;
140
 
                        dnp.FilePropertyChangedInProject -= FilePropertyChangedInProject;;
141
 
                        dnp.FileRemovedFromProject -= FileRemovedFromProject;
142
 
                        dnp.FileChangedInProject -= FileChangedInProject;
143
 
                        dnp.NameChanged -= ProjectNameChanged;
144
 
                        MonoDevelop.Ide.IdeApp.CommandService.ApplicationFocusIn -= AppRegainedFocus;
145
 
                }
146
 
                
147
 
                void ShowXcodeScriptError ()
148
 
                {
149
 
                        MonoDevelop.Ide.MessageService.ShowError (
150
 
                                GettextCatalog.GetString ("MonoDevelop could not communicate with Xcode"),
151
 
                                GettextCatalog.GetString ("If Xcode is still running, please ensure that all changes have been saved and " +
152
 
                                                  "Xcode has been exited before continuing, otherwise any new changes may be lost."));
153
 
                }
154
 
                
155
 
                void AppRegainedFocus (object sender, EventArgs e)
156
 
                {
157
 
                        lock (xcode_lock) {
158
 
                                if (!SyncingEnabled)
159
 
                                        return;
160
 
 
161
 
                                XC4Debug.Log ("MonoDevelop has regained focus.");
162
 
 
163
 
                                using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Synchronizing changes from Xcode..."))) {
164
 
                                        bool projectOpen = false;
165
 
 
166
 
                                        try {
167
 
                                                // Note: Both IsProjectOpen() and SaveProject() may throw TimeoutExceptions or AppleScriptExceptions
168
 
                                                if ((projectOpen = xcode.IsProjectOpen ()))
169
 
                                                        xcode.SaveProject (monitor);
170
 
                                        } catch (Exception ex) {
171
 
                                                ShowXcodeScriptError ();
172
 
                                                monitor.Log.WriteLine ("Xcode failed to save pending changes to project: {0}", ex);
173
 
 
174
 
                                                // Note: This will cause us to disable syncing after we sync whatever we can over from Xcode...
175
 
                                                projectOpen = false;
176
 
                                        }
177
 
 
178
 
                                        try {
179
 
                                                SyncXcodeChanges (monitor);
180
 
                                        } finally {
181
 
                                                if (!projectOpen) {
182
 
                                                        XC4Debug.Log ("Xcode project for '{0}' is not open, disabling syncing.", dnp.Name);
183
 
                                                        DisableSyncing (false);
184
 
                                                }
185
 
                                        }
186
 
                                }
187
 
                        }
188
 
                }
189
 
                
190
 
                bool OpenFileInXcodeProject (IProgressMonitor monitor, string path)
191
 
                {
192
 
                        bool succeeded = false;
193
 
                        
194
 
                        try {
195
 
                                EnableSyncing (monitor);
196
 
                                
197
 
                                if (!UpdateTypes (monitor) || monitor.IsCancelRequested)
198
 
                                        return false;
199
 
                                
200
 
                                if (!UpdateXcodeProject (monitor) || monitor.IsCancelRequested)
201
 
                                        return false;
202
 
                                
203
 
                                xcode.OpenFile (monitor, path);
204
 
                                succeeded = true;
205
 
                        } catch (AppleScriptException asex) {
206
 
                                ShowXcodeScriptError ();
207
 
                                monitor.ReportError (GettextCatalog.GetString ("Could not open file in Xcode project."), asex);
208
 
                        } catch (TimeoutException tex) {
209
 
                                ShowXcodeScriptError ();
210
 
                                monitor.ReportError (GettextCatalog.GetString ("Could not open file in Xcode project."), tex);
211
 
                        } catch (Exception ex) {
212
 
                                monitor.ReportError (GettextCatalog.GetString ("Could not open file in Xcode project."), ex);
213
 
                        } finally {
214
 
                                if (!succeeded)
215
 
                                        DisableSyncing (true);
216
 
                        }
217
 
                        
218
 
                        return succeeded;
219
 
                }
220
 
                
221
 
                public bool OpenDocument (string path)
222
 
                {
223
 
                        var xibFile = dnp.Files.GetFile (path);
224
 
                        bool success = false;
225
 
 
226
 
                        Debug.Assert (xibFile != null);
227
 
                        Debug.Assert (IsInterfaceDefinition (xibFile));
228
 
 
229
 
                        lock (xcode_lock) {
230
 
                                using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Opening document '{0}' from project '{1}' in Xcode...", xibFile.ProjectVirtualPath, dnp.Name))) {
231
 
                                        monitor.BeginTask (GettextCatalog.GetString ("Opening document '{0}' from project '{1}' in Xcode...", xibFile.ProjectVirtualPath, dnp.Name), 0);
232
 
                                        success = OpenFileInXcodeProject (monitor, xibFile.ProjectVirtualPath);
233
 
                                        monitor.EndTask ();
234
 
                                }
235
 
                        }
236
 
 
237
 
                        return success;
238
 
                }
239
 
                
240
 
                static bool IsInterfaceDefinition (ProjectFile pf)
241
 
                {
242
 
                        return pf.BuildAction == BuildAction.InterfaceDefinition;
243
 
                }
244
 
                
245
 
                bool IncludeInSyncedProject (ProjectFile pf)
246
 
                {
247
 
                        return (pf.BuildAction == BuildAction.Content && pf.ProjectVirtualPath.ParentDirectory.IsNullOrEmpty)
248
 
                                || (pf.BuildAction == BuildAction.InterfaceDefinition && HasInterfaceDefinitionExtension (pf.FilePath));
249
 
                }
250
 
                
251
 
                #region Project change tracking
252
 
                
253
 
                void ProjectNameChanged (object sender, SolutionItemRenamedEventArgs e)
254
 
                {
255
 
                        lock (xcode_lock) {
256
 
                                if (!SyncingEnabled)
257
 
                                        return;
258
 
 
259
 
                                XC4Debug.Log ("Project '{0}' was renamed to '{1}', resetting Xcode sync.", e.OldName, e.NewName);
260
 
 
261
 
                                XC4Debug.Indent ();
262
 
                                try {
263
 
                                        xcode.CloseProject ();
264
 
                                        xcode.DeleteProjectDirectory ();
265
 
                                } finally {
266
 
                                        XC4Debug.Unindent ();
267
 
                                }
268
 
 
269
 
                                xcode = new XcodeMonitor (dnp.BaseDirectory.Combine ("obj", "Xcode"), dnp.Name);
270
 
                        }
271
 
                }
272
 
                
273
 
                void FileRemovedFromProject (object sender, ProjectFileEventArgs e)
274
 
                {
275
 
                        lock (xcode_lock) {
276
 
                                if (!SyncingEnabled)
277
 
                                        return;
278
 
 
279
 
                                XC4Debug.Log ("Files removed from project '{0}'", dnp.Name);
280
 
                                foreach (var file in e)
281
 
                                        XC4Debug.Log ("   * Removed: {0}", file.ProjectFile.ProjectVirtualPath);
282
 
 
283
 
                                XC4Debug.Indent ();
284
 
                                try {
285
 
                                        if (e.Any (finf => finf.Project == dnp && IsInterfaceDefinition (finf.ProjectFile))) {
286
 
                                                if (!dnp.Files.Any (IsInterfaceDefinition)) {
287
 
                                                        XC4Debug.Log ("Last Interface Definition file removed from '{0}', disabling Xcode sync.", dnp.Name);
288
 
                                                        DisableSyncing (true);
289
 
                                                        return;
290
 
                                                }
291
 
                                        }
292
 
                                } finally {
293
 
                                        XC4Debug.Unindent ();
294
 
                                }
295
 
 
296
 
                                CheckFileChanges (e);
297
 
                        }
298
 
                }
299
 
 
300
 
                void FileAddedToProject (object sender, ProjectFileEventArgs e)
301
 
                {
302
 
                        lock (xcode_lock) {
303
 
                                if (!SyncingEnabled)
304
 
                                        return;
305
 
 
306
 
                                XC4Debug.Log ("Files added to project '{0}'", dnp.Name);
307
 
                                foreach (var file in e)
308
 
                                        XC4Debug.Log ("   * Added: {0}", file.ProjectFile.ProjectVirtualPath);
309
 
 
310
 
                                CheckFileChanges (e);
311
 
                        }
312
 
                }
313
 
 
314
 
                void FileChangedInProject (object sender, ProjectFileEventArgs e)
315
 
                {
316
 
                        lock (xcode_lock) {
317
 
                                // avoid infinite recursion when we add files
318
 
                                if (!SyncingEnabled || updatingProjectFiles)
319
 
                                        return;
320
 
 
321
 
                                XC4Debug.Log ("Files changed in project '{0}'", dnp.Name);
322
 
                                foreach (var file in e)
323
 
                                        XC4Debug.Log ("   * Changed: {0}", file.ProjectFile.ProjectVirtualPath);
324
 
 
325
 
                                CheckFileChanges (e);
326
 
                        }
327
 
                }
328
 
 
329
 
                void FilePropertyChangedInProject (object sender, ProjectFileEventArgs e)
330
 
                {
331
 
                        lock (xcode_lock) {
332
 
                                if (!SyncingEnabled)
333
 
                                        return;
334
 
 
335
 
                                XC4Debug.Log ("File properties changed in project '{0}'", dnp.Name);
336
 
                                foreach (var file in e)
337
 
                                        XC4Debug.Log ("   * Property Changed: {0}", file.ProjectFile.ProjectVirtualPath);
338
 
 
339
 
                                CheckFileChanges (e);
340
 
                        }
341
 
                }
342
 
 
343
 
                void CheckFileChanges (ProjectFileEventArgs e)
344
 
                {
345
 
                        bool updateTypes = false, updateProject = false;
346
 
                        
347
 
                        foreach (ProjectFileEventInfo finfo in e) {
348
 
                                if (finfo.Project != dnp)
349
 
                                        continue;
350
 
 
351
 
                                if (finfo.ProjectFile.BuildAction == BuildAction.Compile) {
352
 
                                        updateTypes = true;
353
 
                                } else if (IncludeInSyncedProject (finfo.ProjectFile)) {
354
 
                                        updateProject = true;
355
 
                                }
356
 
                        }
357
 
                        
358
 
                        if (updateTypes) {
359
 
                                using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Syncing types to Xcode..."))) {
360
 
                                        //FIXME: make this async (and safely async)
361
 
                                        //FIXME: only update the project if obj-c types change
362
 
                                        updateProject |= UpdateTypes (monitor);
363
 
                                }
364
 
                        }
365
 
                        
366
 
                        if (updateProject) {
367
 
                                using (var monitor = GetStatusMonitor (GettextCatalog.GetString ("Syncing project to Xcode..."))) {
368
 
                                        //FIXME: make this async (and safely async)
369
 
                                        var running = xcode.CheckRunning ();
370
 
                                        UpdateXcodeProject (monitor);
371
 
                                        if (running) {
372
 
                                                try {
373
 
                                                        xcode.OpenProject (monitor);
374
 
                                                } catch (AppleScriptException) {
375
 
                                                        ShowXcodeScriptError ();
376
 
                                                } catch (TimeoutException) {
377
 
                                                        ShowXcodeScriptError ();
378
 
                                                }
379
 
                                        }
380
 
                                }
381
 
                        }
382
 
                }
383
 
                
384
 
                #endregion
385
 
                
386
 
                #region Progress monitors
387
 
                
388
 
                //FIXME: should we use a modal monitor to prevent the user doing unexpected things?
389
 
                IProgressMonitor GetStatusMonitor (string title)
390
 
                {
391
 
                        IProgressMonitor monitor = MonoDevelop.Ide.IdeApp.Workbench.ProgressMonitors.GetStatusProgressMonitor (
392
 
                                title, null, true);
393
 
                        
394
 
                        monitor = new MonoDevelop.Core.ProgressMonitoring.AggregatedProgressMonitor (
395
 
                                monitor, XC4Debug.GetLoggingMonitor ());
396
 
                        
397
 
                        return monitor;
398
 
                }
399
 
                
400
 
                #endregion
401
 
                
402
 
                #region Outbound syncing
403
 
                
404
 
                bool UpdateTypes (IProgressMonitor monitor)
405
 
                {
406
 
                        monitor.BeginTask (GettextCatalog.GetString ("Updating Objective-C type information"), 0);
407
 
                        try {
408
 
                                var pinfo = infoService.GetProjectInfo (dnp);
409
 
                                if (pinfo == null)
410
 
                                        return true;
411
 
                                // FIXME: report progress
412
 
                                pinfo.Update (true);
413
 
                                userTypes = pinfo.GetTypes ().Where (t => t.IsUserType).ToList ();
414
 
                                return true;
415
 
                        } catch (Exception ex) {
416
 
                                monitor.ReportError (GettextCatalog.GetString ("Error updating Objective-C type information"), ex);
417
 
                                return false;
418
 
                        } finally {
419
 
                                monitor.EndTask ();
420
 
                        }
421
 
                }
422
 
                
423
 
                protected abstract XcodeProject CreateProject (string name);
424
 
                
425
 
                bool UpdateXcodeProject (IProgressMonitor monitor)
426
 
                {
427
 
                        try {
428
 
                                // Ensure that the obj directory and all subfiles/subdirectories
429
 
                                // are writeable so we can create the temp files for xcode syncing
430
 
                                dnp.BaseDirectory.MakeWritable ();
431
 
                                ObjDir.MakeWritable ();
432
 
                                XcodeDir.MakeWritable (true);
433
 
                                
434
 
                                xcode.UpdateProject (monitor, CreateSyncList (), CreateProject (dnp.Name));
435
 
                                return true;
436
 
                        } catch (AppleScriptException asex) {
437
 
                                ShowXcodeScriptError ();
438
 
                                monitor.ReportError (GettextCatalog.GetString ("Error updating Xcode project"), asex);
439
 
                                return false;
440
 
                        } catch (TimeoutException tex) {
441
 
                                ShowXcodeScriptError ();
442
 
                                monitor.ReportError (GettextCatalog.GetString ("Error updating Xcode project"), tex);
443
 
                                return false;
444
 
                        } catch (Exception ex) {
445
 
                                monitor.ReportError (GettextCatalog.GetString ("Error updating Xcode project"), ex);
446
 
                                return false;
447
 
                        }
448
 
                }
449
 
                
450
 
                List<XcodeSyncedItem> CreateSyncList ()
451
 
                {
452
 
                        var syncList = new List<XcodeSyncedItem> ();
453
 
                        foreach (var file in dnp.Files.Where (IncludeInSyncedProject))
454
 
                                syncList.Add (new XcodeSyncedContent (file));
455
 
                        foreach (var type in userTypes)
456
 
                                syncList.Add (new XcodeSyncedType (type, GetFrameworks ()));
457
 
                        
458
 
                        return syncList;
459
 
                }
460
 
                
461
 
                #endregion
462
 
                
463
 
                #region Inbound syncing
464
 
                
465
 
                bool SyncXcodeChanges (IProgressMonitor monitor)
466
 
                {
467
 
                        try {
468
 
                                monitor.BeginTask (GettextCatalog.GetString ("Detecting changed files in Xcode..."), 0);
469
 
                                var changeCtx = xcode.GetChanges (monitor, infoService, dnp);
470
 
                                monitor.EndTask ();
471
 
                                
472
 
                                updatingProjectFiles = true;
473
 
                                bool filesAdded = false;
474
 
                                bool typesAdded = false;
475
 
                                
476
 
                                // First, copy any changed/added resource files to MonoDevelop's project directory.
477
 
                                CopyFilesToMD (monitor, changeCtx);
478
 
                                
479
 
                                // Then update CLI types.
480
 
                                if (UpdateCliTypes (monitor, changeCtx, out typesAdded))
481
 
                                        filesAdded = true;
482
 
                                
483
 
                                // Next, parse UI definition files for custom classes
484
 
                                if (AddCustomClassesFromUIDefinitionFiles (monitor, changeCtx))
485
 
                                        typesAdded = true;
486
 
                                
487
 
                                // Finally, add any newly created resource files to the DotNetProject.
488
 
                                if (AddFilesToMD (monitor, changeCtx))
489
 
                                        filesAdded = true;
490
 
                                
491
 
                                // Save the DotNetProject.
492
 
                                if (filesAdded || typesAdded)
493
 
                                        Ide.IdeApp.ProjectOperations.Save (dnp);
494
 
                                
495
 
                                // Notify MonoDevelop of file changes.
496
 
                                Gtk.Application.Invoke (delegate {
497
 
                                        // FIXME: this should probably filter out any IsFreshlyAdded file jobs
498
 
                                        FileService.NotifyFilesChanged (changeCtx.FileSyncJobs.Select (f => f.Original));
499
 
                                });
500
 
                                
501
 
                                if (typesAdded && xcode.CheckRunning ())
502
 
                                        UpdateXcodeProject (monitor);
503
 
                                
504
 
                                return true;
505
 
                        } catch (Exception ex) {
506
 
                                monitor.ReportError (GettextCatalog.GetString ("Error synchronizing changes from Xcode"), ex);
507
 
                                return false;
508
 
                        } finally {
509
 
                                updatingProjectFiles = false;
510
 
                        }
511
 
                }
512
 
                
513
 
                /// <summary>
514
 
                /// Copies resource files from the Xcode project (back) to the MonoDevelop project directory.
515
 
                /// </summary>
516
 
                /// <param name='monitor'>
517
 
                /// A progress monitor.
518
 
                /// </param>
519
 
                /// <param name='context'>
520
 
                /// The sync context.
521
 
                /// </param>
522
 
                void CopyFilesToMD (IProgressMonitor monitor, XcodeSyncBackContext context)
523
 
                {
524
 
                        if (context.FileSyncJobs.Count == 0)
525
 
                                return;
526
 
                        
527
 
                        monitor.BeginStepTask ("Copying files from Xcode back to MonoDevelop...", context.FileSyncJobs.Count, 1);
528
 
                        
529
 
                        foreach (var file in context.FileSyncJobs) {
530
 
                                monitor.Log.WriteLine ("Copying {0} file from Xcode: {1}", file.IsFreshlyAdded ? "new" : "changed", file.SyncedRelative);
531
 
                                
532
 
                                if (!Directory.Exists (file.Original.ParentDirectory))
533
 
                                        Directory.CreateDirectory (file.Original.ParentDirectory);
534
 
                                
535
 
                                var tempFile = file.Original.ParentDirectory.Combine (".#" + file.Original.ParentDirectory.FileName);
536
 
                                FilePath path = context.ProjectDir.Combine (file.SyncedRelative);
537
 
                                
538
 
                                if (File.Exists (path)) {
539
 
                                        File.Copy (path, tempFile);
540
 
                                        FileService.SystemRename (tempFile, file.Original);
541
 
                                        
542
 
                                        DateTime mtime = File.GetLastWriteTime (file.Original);
543
 
                                        context.SetSyncTime (file.SyncedRelative, mtime);
544
 
                                } else {
545
 
                                        monitor.ReportWarning (string.Format ("'{0}' does not exist.", file.SyncedRelative));
546
 
                                }
547
 
                                
548
 
                                monitor.Step (1);
549
 
                        }
550
 
                        
551
 
                        monitor.EndTask ();
552
 
                }
553
 
                
554
 
                /// <summary>
555
 
                /// Adds any newly created resource files to MonoDevelop's DotNetProject.
556
 
                /// </summary>
557
 
                /// <param name='monitor'>
558
 
                /// A progress monitor.
559
 
                /// </param>
560
 
                /// <param name='context'>
561
 
                /// The sync context.
562
 
                /// </param>
563
 
                /// <returns>
564
 
                /// Returns whether or not new files were added to the project.
565
 
                /// </returns>
566
 
                bool AddFilesToMD (IProgressMonitor monitor, XcodeSyncBackContext context)
567
 
                {
568
 
                        bool filesAdded = false;
569
 
                        
570
 
                        if (context.FileSyncJobs.Count == 0)
571
 
                                return false;
572
 
                        
573
 
                        foreach (var file in context.FileSyncJobs) {
574
 
                                if (!file.IsFreshlyAdded)
575
 
                                        continue;
576
 
                                
577
 
                                monitor.Log.WriteLine ("Adding '{0}' to project '{1}'", file.SyncedRelative, dnp.Name);
578
 
                                
579
 
                                FilePath path = new FilePath (file.Original);
580
 
                                string buildAction = HasInterfaceDefinitionExtension (path) ? BuildAction.InterfaceDefinition : BuildAction.Content;
581
 
                                context.Project.AddFile (path, buildAction);
582
 
                                filesAdded = true;
583
 
                        }
584
 
                        
585
 
                        return filesAdded;
586
 
                }
587
 
                
588
 
                protected virtual IEnumerable<NSObjectTypeInfo> GetCustomTypesFromUIDefinition (FilePath fileName)
589
 
                {
590
 
                        yield break;
591
 
                }
592
 
                
593
 
                /// <summary>
594
 
                /// Adds the custom classes from user interface definition files.
595
 
                /// </summary>
596
 
                /// <returns>
597
 
                /// <c>true</c> if new types were added to the project, or <c>false</c> otherwise.
598
 
                /// </returns>
599
 
                /// <param name='monitor'>
600
 
                /// A progress monitor.
601
 
                /// </param>
602
 
                /// <param name='context'>
603
 
                /// A sync-back context.
604
 
                /// </param>
605
 
                bool AddCustomClassesFromUIDefinitionFiles (IProgressMonitor monitor, XcodeSyncBackContext context)
606
 
                {
607
 
                        var provider = dnp.LanguageBinding.GetCodeDomProvider ();
608
 
                        var options = new System.CodeDom.Compiler.CodeGeneratorOptions ();
609
 
                        var writer = MonoDevelop.DesignerSupport.CodeBehindWriter.CreateForProject (
610
 
                                new MonoDevelop.Core.ProgressMonitoring.NullProgressMonitor (), dnp);
611
 
                        bool addedTypes = false;
612
 
                        bool beganTask = false;
613
 
                        
614
 
                        // Collect our list of custom classes from UI definition files
615
 
                        foreach (var job in context.FileSyncJobs) {
616
 
                                if (!HasInterfaceDefinitionExtension (job.Original))
617
 
                                        continue;
618
 
                                
619
 
                                if (!beganTask) {
620
 
                                        monitor.BeginTask (GettextCatalog.GetString ("Generating custom classes defined in UI definition files..."), 0);
621
 
                                        beganTask = true;
622
 
                                }
623
 
                                
624
 
                                string relative = job.SyncedRelative.ParentDirectory;
625
 
                                string dir = dnp.BaseDirectory;
626
 
                                
627
 
                                if (!string.IsNullOrEmpty (relative))
628
 
                                        dir = Path.Combine (dir, relative);
629
 
                                
630
 
                                foreach (var type in GetCustomTypesFromUIDefinition (job.Original)) {
631
 
                                        if (context.ProjectInfo.ContainsType (type.ObjCName))
632
 
                                                continue;
633
 
                                        
634
 
                                        string designerPath = Path.Combine (dir, type.ObjCName + ".designer." + provider.FileExtension);
635
 
                                        string path = Path.Combine (dir, type.ObjCName + "." + provider.FileExtension);
636
 
                                        string ns = dnp.GetDefaultNamespace (path);
637
 
                                        
638
 
                                        type.CliName = ns + "." + provider.CreateValidIdentifier (type.ObjCName);
639
 
                                        
640
 
                                        if (provider is Microsoft.CSharp.CSharpCodeProvider) {
641
 
                                                CodebehindTemplateBase cs = new CSharpCodeTypeDefinition () {
642
 
                                                        WrapperNamespace = infoService.WrapperRoot,
643
 
                                                        Provider = provider,
644
 
                                                        Type = type,
645
 
                                                };
646
 
                                                
647
 
                                                writer.WriteFile (path, cs.TransformText ());
648
 
                                                
649
 
                                                List<NSObjectTypeInfo> types = new List<NSObjectTypeInfo> ();
650
 
                                                types.Add (type);
651
 
                                                
652
 
                                                cs = new CSharpCodeCodebehind () {
653
 
                                                        WrapperNamespace = infoService.WrapperRoot,
654
 
                                                        Provider = provider,
655
 
                                                        Types = types,
656
 
                                                };
657
 
                                                
658
 
                                                writer.WriteFile (designerPath, cs.TransformText ());
659
 
                                                
660
 
                                                context.ProjectInfo.InsertUpdatedType (type);
661
 
                                        } else {
662
 
                                                // FIXME: implement support for non-C# languages
663
 
                                        }
664
 
                                        
665
 
                                        dnp.AddFile (new ProjectFile (path));
666
 
                                        dnp.AddFile (new ProjectFile (designerPath) { DependsOn = path });
667
 
                                        addedTypes = true;
668
 
                                }
669
 
                        }
670
 
                        
671
 
                        writer.WriteOpenFiles ();
672
 
                        
673
 
                        if (beganTask)
674
 
                                monitor.EndTask ();
675
 
                        
676
 
                        return addedTypes;
677
 
                }
678
 
                
679
 
                /// <summary>
680
 
                /// Updates the cli types.
681
 
                /// </summary>
682
 
                /// <returns>
683
 
                /// Returns whether or not any files were added to the project.
684
 
                /// </returns>
685
 
                /// <param name='monitor'>
686
 
                /// A progress monitor.
687
 
                /// </param>
688
 
                /// <param name='context'>
689
 
                /// A sync-back context.
690
 
                /// </param>
691
 
                /// <param name='typesAdded'>
692
 
                /// An output variable specifying whether or not any types were added to the project.
693
 
                /// </param>
694
 
                bool UpdateCliTypes (IProgressMonitor monitor, XcodeSyncBackContext context, out bool typesAdded)
695
 
                {
696
 
                        var provider = dnp.LanguageBinding.GetCodeDomProvider ();
697
 
                        var options = new System.CodeDom.Compiler.CodeGeneratorOptions ();
698
 
                        var writer = MonoDevelop.DesignerSupport.CodeBehindWriter.CreateForProject (
699
 
                                new MonoDevelop.Core.ProgressMonitoring.NullProgressMonitor (), dnp);
700
 
                        
701
 
                        monitor.BeginTask (GettextCatalog.GetString ("Detecting changes made in Xcode to custom user types..."), 0);
702
 
                        Dictionary<string, NSObjectTypeInfo> newTypes;
703
 
                        Dictionary<string, ProjectFile> newFiles;
704
 
                        var updates = context.GetTypeUpdates (monitor, provider, out newTypes, out newFiles);
705
 
                        if ((updates == null || updates.Count == 0) && newTypes == null && newFiles == null) {
706
 
                                monitor.Log.WriteLine ("No changes found.");
707
 
                                monitor.EndTask ();
708
 
                                typesAdded = false;
709
 
                                return false;
710
 
                        }
711
 
                        monitor.EndTask ();
712
 
                        
713
 
                        int count = updates.Count + (newTypes != null ? newTypes.Count : 0);
714
 
                        monitor.BeginTask (GettextCatalog.GetString ("Updating custom user types in MonoDevelop..."), count);
715
 
                        
716
 
                        // First, add new types...
717
 
                        if (newTypes != null && newTypes.Count > 0) {
718
 
                                foreach (var nt in newTypes) {
719
 
                                        monitor.Log.WriteLine ("Adding new type: {0}", nt.Value.CliName);
720
 
                                        
721
 
                                        if (provider is Microsoft.CSharp.CSharpCodeProvider) {
722
 
                                                var cs = new CSharpCodeTypeDefinition () {
723
 
                                                        WrapperNamespace = infoService.WrapperRoot,
724
 
                                                        Provider = provider,
725
 
                                                        Type = nt.Value,
726
 
                                                };
727
 
                                                
728
 
                                                string baseDir = Path.GetDirectoryName (nt.Key);
729
 
                                                if (!Directory.Exists (baseDir))
730
 
                                                        Directory.CreateDirectory (baseDir);
731
 
                                                
732
 
                                                writer.WriteFile (nt.Key, cs.TransformText ());
733
 
                                        } else {
734
 
                                                // FIXME: implement support for non-C# languages
735
 
                                        }
736
 
                                        
737
 
                                        monitor.Step (1);
738
 
                                }
739
 
                                
740
 
                                typesAdded = true;
741
 
                        } else {
742
 
                                typesAdded = false;
743
 
                        }
744
 
                        
745
 
                        // Next, generate the designer files for any added/changed types
746
 
                        foreach (var df in updates) {
747
 
                                monitor.Log.WriteLine ("Generating designer file: {0}", df.Key);
748
 
                                if (provider is Microsoft.CSharp.CSharpCodeProvider) {
749
 
                                        var cs = new CSharpCodeCodebehind () {
750
 
                                                Types = df.Value,
751
 
                                                WrapperNamespace = infoService.WrapperRoot,
752
 
                                                Provider = provider,
753
 
                                        };
754
 
                                        writer.WriteFile (df.Key, cs.TransformText ());
755
 
                                } else {
756
 
                                        var ccu = GenerateCompileUnit (provider, options, df.Key, df.Value);
757
 
                                        writer.WriteFile (df.Key, ccu);
758
 
                                }
759
 
                                
760
 
                                monitor.Step (1);
761
 
                        }
762
 
                        
763
 
                        writer.WriteOpenFiles ();
764
 
                        
765
 
                        // Update sync timestamps
766
 
                        foreach (var df in updates) {
767
 
                                foreach (var type in df.Value)
768
 
                                        context.SetSyncTime (type.ObjCName + ".h", DateTime.Now);
769
 
                        }
770
 
                        
771
 
                        // Add new files to the DotNetProject
772
 
                        if (newFiles != null) {
773
 
                                foreach (var f in newFiles) {
774
 
                                        monitor.Log.WriteLine ("Added new designer file: {0}", f.Key);
775
 
                                        dnp.AddFile (f.Value);
776
 
                                }
777
 
                        }
778
 
                        
779
 
                        monitor.EndTask ();
780
 
                        
781
 
                        return newFiles != null && newFiles.Count > 0;
782
 
                }
783
 
                
784
 
                System.CodeDom.CodeCompileUnit GenerateCompileUnit (System.CodeDom.Compiler.CodeDomProvider provider,
785
 
                        System.CodeDom.Compiler.CodeGeneratorOptions options, string file, List<NSObjectTypeInfo> types)
786
 
                {
787
 
                        var ccu = new System.CodeDom.CodeCompileUnit ();
788
 
                        var namespaces = new Dictionary<string, System.CodeDom.CodeNamespace> ();
789
 
                        foreach (var t in types) {
790
 
                                System.CodeDom.CodeTypeDeclaration type;
791
 
                                string nsName;
792
 
                                System.CodeDom.CodeNamespace ns;
793
 
                                t.GenerateCodeTypeDeclaration (provider, options, infoService.WrapperRoot, out type, out nsName);
794
 
                                if (!namespaces.TryGetValue (nsName, out ns)) {
795
 
                                        namespaces[nsName] = ns = new System.CodeDom.CodeNamespace (nsName);
796
 
                                        ccu.Namespaces.Add (ns);
797
 
                                }
798
 
                                ns.Types.Add (type);
799
 
                        }
800
 
                        return ccu;
801
 
                }
802
 
                
803
 
                #endregion
804
 
                
805
 
                public void Dispose ()
806
 
                {
807
 
                        if (disposed)
808
 
                                return;
809
 
                        
810
 
                        disposed = true;
811
 
                        DisableSyncing (true);
812
 
                        AppleSdkSettings.Changed -= DisableSyncing;
813
 
                }
814
 
        }
815
 
        
816
 
        static class XC4Debug
817
 
        {
818
 
                static int indentLevel = 0;
819
 
                static TextWriter writer;
820
 
                
821
 
                static XC4Debug ()
822
 
                {
823
 
                        FilePath logDir = UserProfile.Current.LogDir;
824
 
                        FilePath logFile = logDir.Combine (UniqueLogFile);
825
 
                        
826
 
                        FileService.EnsureDirectoryExists (logDir);
827
 
                        
828
 
                        try {
829
 
                                var stream = File.Open (logFile, FileMode.Create, FileAccess.Write, FileShare.Read);
830
 
                                writer = new StreamWriter (stream) { AutoFlush = true };
831
 
                        } catch (Exception ex) {
832
 
                                LoggingService.LogError ("Could not create Xcode sync logging file", ex);
833
 
                        }
834
 
                }
835
 
                
836
 
                static string UniqueLogFile {
837
 
                        get {
838
 
                                return string.Format ("Xcode4Sync-{0}.log", LoggingService.LogTimestamp.ToString ("yyyy-MM-dd__HH-mm-ss"));
839
 
                        }
840
 
                }
841
 
                
842
 
                static string TimeStamp {
843
 
                        get { return string.Format ("[{0}] ", DateTime.Now.ToString ("yyyy-MM-dd HH:mm:ss.f")); }
844
 
                }
845
 
                
846
 
                public static void Indent ()
847
 
                {
848
 
                        indentLevel++;
849
 
                }
850
 
                
851
 
                public static void Unindent ()
852
 
                {
853
 
                        indentLevel--;
854
 
                }
855
 
                
856
 
                public static void Log (string message)
857
 
                {
858
 
                        if (writer == null)
859
 
                                return;
860
 
                        
861
 
                        writer.WriteLine (TimeStamp + new string (' ', indentLevel * 3) + message);
862
 
                }
863
 
                
864
 
                public static void Log (string format, params object[] args)
865
 
                {
866
 
                        if (writer == null)
867
 
                                return;
868
 
                        
869
 
                        writer.WriteLine (TimeStamp + new string (' ', indentLevel * 3) + format, args);
870
 
                }
871
 
                
872
 
                public static IProgressMonitor GetLoggingMonitor ()
873
 
                {
874
 
                        if (writer == null)
875
 
                                return new MonoDevelop.Core.ProgressMonitoring.NullProgressMonitor ();
876
 
                        
877
 
                        return new MonoDevelop.Core.ProgressMonitoring.ConsoleProgressMonitor (writer) { EnableTimeStamp = true, WrapText = false };
878
 
                }
879
 
        }
880
 
}