Selective Sync now shows both server AND local folders
[pithos-ms-client] / trunk / Pithos.Client.WPF / SelectiveSynch / SelectiveSynchViewModel.cs
1 #region\r
2 /* -----------------------------------------------------------------------\r
3  * <copyright file="SelectiveSynchViewModel.cs" company="GRNet">\r
4  * \r
5  * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
6  *\r
7  * Redistribution and use in source and binary forms, with or\r
8  * without modification, are permitted provided that the following\r
9  * conditions are met:\r
10  *\r
11  *   1. Redistributions of source code must retain the above\r
12  *      copyright notice, this list of conditions and the following\r
13  *      disclaimer.\r
14  *\r
15  *   2. Redistributions in binary form must reproduce the above\r
16  *      copyright notice, this list of conditions and the following\r
17  *      disclaimer in the documentation and/or other materials\r
18  *      provided with the distribution.\r
19  *\r
20  *\r
21  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
22  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
24  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
25  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
32  * POSSIBILITY OF SUCH DAMAGE.\r
33  *\r
34  * The views and conclusions contained in the software and\r
35  * documentation are those of the authors and should not be\r
36  * interpreted as representing official policies, either expressed\r
37  * or implied, of GRNET S.A.\r
38  * </copyright>\r
39  * -----------------------------------------------------------------------\r
40  */\r
41 #endregion\r
42 using System;\r
43 using System.Collections.Generic;\r
44 using System.Collections.ObjectModel;\r
45 using System.Linq;\r
46 using System.Threading.Tasks;\r
47 using Caliburn.Micro;\r
48 using Pithos.Client.WPF.Properties;\r
49 using Pithos.Client.WPF.Utils;\r
50 using Pithos.Core;\r
51 using Pithos.Interfaces;\r
52 using Pithos.Network;\r
53 \r
54 namespace Pithos.Client.WPF.SelectiveSynch\r
55 {\r
56     class SelectiveSynchViewModel:Screen\r
57     {        \r
58         private readonly IEventAggregator _events ;\r
59 \r
60 \r
61         public AccountSettings Account { get; set; }\r
62 \r
63         private readonly ObservableCollection<DirectoryRecord> _rootNodes=new ObservableCollection<DirectoryRecord>();\r
64         public ObservableCollection<DirectoryRecord> RootNodes\r
65         {\r
66             get { return _rootNodes; }\r
67         }\r
68 \r
69         private ObservableCollection<ObjectInfo> _checks;\r
70         //private readonly PithosMonitor _monitor;\r
71         private bool _isBusy=true;\r
72         private readonly string _apiKey;\r
73 \r
74         public ObservableCollection<ObjectInfo> Checks\r
75         {\r
76             get { return _checks; }\r
77         }\r
78 \r
79         public void GetChecks()\r
80         {\r
81             var root = RootNodes[0];            \r
82             _checks = new ObservableCollection<ObjectInfo>(\r
83                 from DirectoryRecord record in root\r
84                 where record.IsChecked==true\r
85                 select record.ObjectInfo);\r
86             NotifyOfPropertyChange(() => Checks);\r
87         }\r
88 \r
89         public SelectiveSynchViewModel(/*PithosMonitor monitor,*/ IEventAggregator events, AccountSettings account, string apiKey)\r
90         {\r
91             Account = account;\r
92             AccountName = account.AccountName;\r
93             DisplayName = String.Format("Selective folder synchronization for {0}",account.AccountName);\r
94             //_monitor = monitor;\r
95             _events = events;\r
96             _apiKey = apiKey;\r
97             TaskEx.Run(LoadRootNode);\r
98         }\r
99 \r
100         private void LoadRootNode()\r
101         {            \r
102             //TODO: Check this\r
103             var client = new CloudFilesClient(AccountName,_apiKey){AuthenticationUrl=Account.ServerUrl,UsePithos=true};\r
104             client.Authenticate();\r
105             \r
106             //NEED to get the local folders here as well,\r
107             // and combine them with the cloud folders\r
108 \r
109 \r
110             var dirs = from container in client.ListContainers(AccountName)  \r
111                        where container.Name != "trash"\r
112                        select new DirectoryRecord\r
113                                   {\r
114                                       DisplayName = container.Name,\r
115                                       Uri=new Uri(client.StorageUrl,String.Format(@"{0}/{1}",Account.AccountName, container.Name)),\r
116                                       Directories = (from dir in client.ListObjects(AccountName, container.Name)                                                                                                          \r
117                                                      select dir).ToTree()\r
118                                   };\r
119             var ownFolders = dirs.ToList();\r
120 \r
121 \r
122 \r
123             var accountNodes=from account in client.ListSharingAccounts()\r
124                              select new DirectoryRecord\r
125                              {\r
126                                 DisplayName=account.name,\r
127                                 Uri=new Uri(client.StorageUrl,"../"+ account.name),\r
128                                 Directories=(from container in client.ListContainers(account.name)\r
129                                             select new DirectoryRecord\r
130                                                         {\r
131                                                             DisplayName=container.Name,\r
132                                                             Uri = new Uri(client.StorageUrl, "../" + account.name + "/" + container.Name),\r
133                                                             Directories=(from folder in client.ListObjects(account.name,container.Name)                                                                        \r
134                                                                         select folder).ToTree()\r
135                                                         }).ToList()\r
136                              };                                                          \r
137 \r
138             var othersNode = new DirectoryRecord\r
139                                  {\r
140                                      DisplayName = "Shared to me",\r
141                                      Directories=accountNodes.ToList()\r
142                                  };\r
143 \r
144             \r
145             var rootItem = new DirectoryRecord\r
146                                {\r
147                                    DisplayName = AccountName ,\r
148                                    Directories = ownFolders.ToList()\r
149                                };\r
150 \r
151             var localFolders = SelectiveExtensions.LocalFolders(AccountName, Account.RootPath,client.StorageUrl.AbsoluteUri);\r
152 \r
153             AppendToTree(localFolders, rootItem);\r
154             \r
155             //For each local folder that doesn't exist in the server nodes \r
156             //find the best matching parent and add the folder below it.\r
157 \r
158             Execute.OnUIThread(() =>\r
159                                    {\r
160                                        RootNodes.Add(rootItem);\r
161                                        RootNodes.Add(othersNode);\r
162                                    });\r
163 \r
164             SetInitialSelections(Account);\r
165             \r
166             IsBusy = false;\r
167         }\r
168 \r
169         private static void AppendToTree(IEnumerable<DirectoryRecord> localFolders, DirectoryRecord rootItem)\r
170         {\r
171             foreach (var folder in localFolders)\r
172             {\r
173                 var items = from root in rootItem\r
174                             from child in root\r
175                             select child;\r
176                 //If this folder is not included in the server folders\r
177                 if (items.Any(dir => dir.Uri == folder.Uri)) \r
178                     continue;\r
179 \r
180                 //we need to add it\r
181                 //Find the best parent\r
182 \r
183                 //One way to do this, is to break the the Uri to its parts\r
184                 //and try to find a parent using progressively fewer parts\r
185                 var parts = folder.Uri.AbsoluteUri.Split('/');\r
186                 for (var i = parts.Length - 1; i > 2; i--)\r
187                 {\r
188                     var parentUrl = String.Join("/", parts.Splice(0, i));\r
189                     var parentUri = new Uri(parentUrl);\r
190 \r
191                     var parent = items.FirstOrDefault(dir => dir.Uri == parentUri);\r
192                     if (parent != null)\r
193                     {\r
194                         parent.Directories.Add(folder);\r
195                         break;\r
196                     }\r
197                 }\r
198             }\r
199         }\r
200 \r
201         public bool IsBusy\r
202         {\r
203             get {\r
204                 return _isBusy;\r
205             }\r
206             set {\r
207                 _isBusy = value;\r
208                 NotifyOfPropertyChange(()=>IsBusy);\r
209             }\r
210         }\r
211 \r
212         private void SetInitialSelections(AccountSettings account)\r
213         {\r
214             var selections = account.SelectiveFolders;\r
215 \r
216 \r
217                 \r
218             //Initially, all nodes are checked\r
219             //We need to *uncheck* the nodes that are not selected\r
220 \r
221             var allNodes = (from DirectoryRecord rootRecord in RootNodes\r
222                            from DirectoryRecord record in rootRecord\r
223                            select record).ToList();\r
224             //WARNING: Using IsChecked marks the item as REMOVED\r
225             allNodes.Apply(record => record.IsExplicitlyChecked = false);\r
226 \r
227             if (selections.Count == 0)\r
228             {\r
229             //    allNodes.Apply(record => record.IsChecked = false);\r
230                 return;\r
231             } \r
232             \r
233             var selects = (from DirectoryRecord rootRecord in RootNodes\r
234                           from DirectoryRecord record in rootRecord\r
235                           where record.Uri !=null &&  selections.Contains(record.Uri.ToString())\r
236                           select record).ToList();\r
237             //var shouldBeChecked = allNodes.Except(selects).ToList();\r
238             \r
239             //WARNING: Using IsChecked marks the item as ADDED\r
240             selects.Apply(record=>record.IsExplicitlyChecked=true);\r
241 \r
242             //shouldBeChecked.Apply(record => record.IsChecked = true);\r
243             \r
244             \r
245 \r
246         }\r
247 \r
248         protected string AccountName { get; set; }\r
249 \r
250         public void SaveChanges()\r
251         {\r
252             var uris = (from DirectoryRecord root in RootNodes\r
253                         from DirectoryRecord record in root\r
254                         where record.IsChecked == true && record.Uri != null\r
255                         select record.Uri).ToArray();            \r
256 \r
257             SaveSettings(uris);\r
258             \r
259             //RootNodes is an ObservableCollection, it can't be enumerated iterativelly\r
260 \r
261             var added = (from DirectoryRecord root in RootNodes\r
262                          from DirectoryRecord record in root\r
263                          where record.Added && record.Uri != null\r
264                          select record.Uri).ToArray();\r
265             var removed = (from DirectoryRecord root in RootNodes\r
266                            from DirectoryRecord record in root\r
267                           where record.Removed && record.Uri != null\r
268                          select record.Uri).ToArray();\r
269             //TODO: Include Uris for the containers as well\r
270             _events.Publish(new SelectiveSynchChanges{Account=Account,Uris=uris,Added=added,Removed=removed});\r
271             \r
272 \r
273             \r
274 \r
275             TryClose(true);\r
276         }\r
277 \r
278         \r
279         private void SaveSettings(IEnumerable<Uri> uris)\r
280         {\r
281             var selections = uris.Select(uri => uri.ToString()).ToArray();\r
282 \r
283             Account.SelectiveFolders.Clear();\r
284             Account.SelectiveFolders.AddRange(selections);\r
285             Settings.Default.Save();            \r
286         }        \r
287 \r
288         public void RejectChanges()\r
289         {\r
290             TryClose(false);\r
291         }\r
292     }\r
293 }\r