Updated folder statistics
[pithos] / web_client / src / gr / grnet / pithos / web / client / Pithos.java
1 /*
2  * Copyright 2011 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35 package gr.grnet.pithos.web.client;
36
37 import gr.grnet.pithos.web.client.commands.UploadFileCommand;
38 import gr.grnet.pithos.web.client.foldertree.AccountResource;
39 import gr.grnet.pithos.web.client.foldertree.File;
40 import gr.grnet.pithos.web.client.foldertree.Folder;
41 import gr.grnet.pithos.web.client.foldertree.FolderTreeView;
42 import gr.grnet.pithos.web.client.foldertree.FolderTreeViewModel;
43 import gr.grnet.pithos.web.client.foldertree.Resource;
44 import gr.grnet.pithos.web.client.grouptree.Group;
45 import gr.grnet.pithos.web.client.grouptree.GroupTreeView;
46 import gr.grnet.pithos.web.client.grouptree.GroupTreeViewModel;
47 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeView;
48 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeViewModel;
49 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeView;
50 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeViewModel;
51 import gr.grnet.pithos.web.client.rest.DeleteRequest;
52 import gr.grnet.pithos.web.client.rest.GetRequest;
53 import gr.grnet.pithos.web.client.rest.HeadRequest;
54 import gr.grnet.pithos.web.client.rest.PutRequest;
55 import gr.grnet.pithos.web.client.rest.RestException;
56 import gr.grnet.pithos.web.client.tagtree.Tag;
57 import gr.grnet.pithos.web.client.tagtree.TagTreeView;
58 import gr.grnet.pithos.web.client.tagtree.TagTreeViewModel;
59
60 import java.util.ArrayList;
61 import java.util.HashMap;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.Set;
65
66 import com.google.gwt.core.client.EntryPoint;
67 import com.google.gwt.core.client.GWT;
68 import com.google.gwt.core.client.Scheduler;
69 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
70 import com.google.gwt.event.dom.client.ClickEvent;
71 import com.google.gwt.event.dom.client.ClickHandler;
72 import com.google.gwt.event.logical.shared.ResizeEvent;
73 import com.google.gwt.event.logical.shared.ResizeHandler;
74 import com.google.gwt.http.client.Request;
75 import com.google.gwt.http.client.RequestBuilder;
76 import com.google.gwt.http.client.RequestCallback;
77 import com.google.gwt.http.client.RequestException;
78 import com.google.gwt.http.client.Response;
79 import com.google.gwt.json.client.JSONArray;
80 import com.google.gwt.json.client.JSONObject;
81 import com.google.gwt.json.client.JSONParser;
82 import com.google.gwt.json.client.JSONString;
83 import com.google.gwt.json.client.JSONValue;
84 import com.google.gwt.resources.client.ImageResource;
85 import com.google.gwt.user.client.Command;
86 import com.google.gwt.user.client.Cookies;
87 import com.google.gwt.user.client.Event;
88 import com.google.gwt.user.client.History;
89 import com.google.gwt.user.client.Window;
90 import com.google.gwt.user.client.ui.AbstractImagePrototype;
91 import com.google.gwt.user.client.ui.Button;
92 import com.google.gwt.user.client.ui.HTML;
93 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
94 import com.google.gwt.user.client.ui.HasVerticalAlignment;
95 import com.google.gwt.user.client.ui.HorizontalPanel;
96 import com.google.gwt.user.client.ui.HorizontalSplitPanel;
97 import com.google.gwt.user.client.ui.RootPanel;
98 import com.google.gwt.user.client.ui.VerticalPanel;
99 import com.google.gwt.view.client.SelectionChangeEvent;
100 import com.google.gwt.view.client.SelectionChangeEvent.Handler;
101 import com.google.gwt.view.client.SingleSelectionModel;
102
103 /**
104  * Entry point classes define <code>onModuleLoad()</code>.
105  */
106 public class Pithos implements EntryPoint, ResizeHandler {
107
108         public static final String HOME_CONTAINER = "pithos";
109
110         public static final String TRASH_CONTAINER = "trash";
111         
112         /**
113          * Instantiate an application-level image bundle. This object will provide
114          * programmatic access to all the images needed by widgets.
115          */
116         private static Images images = (Images) GWT.create(Images.class);
117
118     public String getUsername() {
119         return username;
120     }
121
122     public void setAccount(AccountResource acct) {
123         account = acct;
124     }
125
126     public AccountResource getAccount() {
127         return account;
128     }
129
130     public void updateFolder(Folder f, boolean showfiles, Command callback) {
131         folderTreeView.updateFolder(f, showfiles, callback);
132     }
133
134     public void updateGroupNode(Group group) {
135         groupTreeView.updateGroupNode(group);
136     }
137
138     public void updateSharedFolder(Folder f, boolean showfiles) {
139         mysharedTreeView.updateFolder(f, showfiles);
140     }
141     
142     public void updateOtherSharedFolder(Folder f, boolean showfiles) {
143         otherSharedTreeView.updateFolder(f, showfiles);
144     }
145
146     public void updateTag(Tag t) {
147         tagTreeView.updateTag(t);
148     }
149
150     public void updateTags() {
151         tagTreeViewModel.initialize(getAllTags());
152     }
153
154     public List<Tag> getAllTags() {
155         List<Tag> tagList = new ArrayList<Tag>();
156         for (Folder f : account.getContainers()) {
157             for (String t : f.getTags()) {
158                 tagList.add(new Tag(t));
159             }
160         }
161         return tagList;
162     }
163
164     public MysharedTreeView getMySharedTreeView() {
165         return mysharedTreeView;
166     }
167
168     /**
169          * An aggregate image bundle that pulls together all the images for this
170          * application into a single bundle.
171          */
172         public interface Images extends TopPanel.Images, FileList.Images {
173
174                 @Source("gr/grnet/pithos/resources/document.png")
175                 ImageResource folders();
176
177                 @Source("gr/grnet/pithos/resources/edit_group_22.png")
178                 ImageResource groups();
179
180                 @Source("gr/grnet/pithos/resources/search.png")
181                 ImageResource search();
182         }
183
184         /**
185          * The Application Clipboard implementation;
186          */
187         private Clipboard clipboard = new Clipboard();
188
189         /**
190          * The top panel that contains the menu bar.
191          */
192         private TopPanel topPanel;
193
194         /**
195          * The panel that contains the various system messages.
196          */
197         private MessagePanel messagePanel = new MessagePanel(Pithos.images);
198
199         /**
200          * The bottom panel that contains the status bar.
201          */
202         private StatusPanel statusPanel = null;
203
204         /**
205          * The file list widget.
206          */
207         private FileList fileList;
208
209         /**
210          * The tab panel that occupies the right side of the screen.
211          */
212         private VerticalPanel inner = new VerticalPanel();
213
214
215         /**
216          * The split panel that will contain the left and right panels.
217          */
218         private HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
219
220         /**
221          * The currently selected item in the application, for use by the Edit menu
222          * commands. Potential types are Folder, File, User and Group.
223          */
224         private Object currentSelection;
225
226
227         /**
228          * The WebDAV password of the current user
229          */
230         private String webDAVPassword;
231
232         public HashMap<String, String> userFullNameMap = new HashMap<String, String>();
233
234     private String username = null;
235
236     /**
237      * The authentication token of the current user.
238      */
239     private String token;
240
241     SingleSelectionModel<Folder> folderTreeSelectionModel;
242     FolderTreeViewModel folderTreeViewModel;
243     FolderTreeView folderTreeView;
244
245     SingleSelectionModel<Folder> mysharedTreeSelectionModel;
246     private MysharedTreeViewModel mysharedTreeViewModel;
247     MysharedTreeView mysharedTreeView;
248
249     protected SingleSelectionModel<Folder> otherSharedTreeSelectionModel;
250     private OtherSharedTreeViewModel otherSharedTreeViewModel;
251     OtherSharedTreeView otherSharedTreeView;
252
253     protected SingleSelectionModel<Tag> tagTreeSelectionModel;
254     private TagTreeViewModel tagTreeViewModel;
255     private TagTreeView tagTreeView;
256
257     GroupTreeViewModel groupTreeViewModel;
258     private GroupTreeView groupTreeView;
259
260     private TreeView selectedTree;
261     protected AccountResource account;
262     
263     Folder trash;
264
265     @SuppressWarnings("rawtypes")
266         private List<SingleSelectionModel> selectionModels = new ArrayList<SingleSelectionModel>();
267     
268     Button upload;
269     
270     private HTML totalFiles;
271     
272     private HTML usedBytes;
273     
274     private HTML totalBytes;
275     
276     private HTML usedPercent;
277     
278     private HTML numOfFiles;
279
280         @Override
281         public void onModuleLoad() {
282                 if (parseUserCredentials())
283             initialize();
284         }
285
286     private void initialize() {
287         VerticalPanel outer = new VerticalPanel();
288         outer.setWidth("100%");
289
290         topPanel = new TopPanel(this, Pithos.images);
291         topPanel.setWidth("100%");
292         outer.add(topPanel);
293
294         messagePanel.setWidth("100%");
295         messagePanel.setVisible(false);
296         outer.add(messagePanel);
297         outer.setCellHorizontalAlignment(messagePanel, HasHorizontalAlignment.ALIGN_CENTER);
298
299
300         // Inner contains the various lists.
301         inner.sinkEvents(Event.ONCONTEXTMENU);
302         inner.setWidth("100%");
303
304         HorizontalPanel rightside = new HorizontalPanel();
305         rightside.addStyleName("pithos-rightSide");
306         rightside.setSpacing(5);
307
308         HorizontalPanel folderStatistics = new HorizontalPanel();
309         folderStatistics.addStyleName("pithos-folderStatistics");
310         numOfFiles = new HTML();
311         folderStatistics.add(numOfFiles);
312         HTML numOfFilesLabel = new HTML("&nbsp;Files");
313         folderStatistics.add(numOfFilesLabel);
314         rightside.add(folderStatistics);
315         inner.add(rightside);
316         inner.setCellHorizontalAlignment(rightside, HasHorizontalAlignment.ALIGN_RIGHT);
317         inner.setCellVerticalAlignment(rightside, HasVerticalAlignment.ALIGN_MIDDLE);
318         inner.setCellHeight(rightside, "60px");
319
320         folderTreeSelectionModel = new SingleSelectionModel<Folder>();
321         folderTreeSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
322             @Override
323             public void onSelectionChange(@SuppressWarnings("unused") SelectionChangeEvent event) {
324                 if (folderTreeSelectionModel.getSelectedObject() != null) {
325                     deselectOthers(folderTreeView, folderTreeSelectionModel);
326                     applyPermissions(folderTreeSelectionModel.getSelectedObject());
327                     Folder f = folderTreeSelectionModel.getSelectedObject();
328                     updateFolder(f, true, null);
329                 }
330             }
331         });
332         selectionModels.add(folderTreeSelectionModel);
333
334         folderTreeViewModel = new FolderTreeViewModel(this, folderTreeSelectionModel);
335         folderTreeView = new FolderTreeView(folderTreeViewModel);
336
337         fileList = new FileList(this, images, folderTreeView);
338         inner.add(fileList);
339
340         mysharedTreeSelectionModel = new SingleSelectionModel<Folder>();
341         mysharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
342             @Override
343             public void onSelectionChange(@SuppressWarnings("unused") SelectionChangeEvent event) {
344                 if (mysharedTreeSelectionModel.getSelectedObject() != null) {
345                     deselectOthers(mysharedTreeView, mysharedTreeSelectionModel);
346                     upload.setEnabled(false);
347                     updateSharedFolder(mysharedTreeSelectionModel.getSelectedObject(), true);
348                 }
349             }
350         });
351         selectionModels.add(mysharedTreeSelectionModel);
352         mysharedTreeViewModel = new MysharedTreeViewModel(this, mysharedTreeSelectionModel);
353         mysharedTreeView = new MysharedTreeView(mysharedTreeViewModel);
354
355         otherSharedTreeSelectionModel = new SingleSelectionModel<Folder>();
356         otherSharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
357             @Override
358             public void onSelectionChange(@SuppressWarnings("unused") SelectionChangeEvent event) {
359                 if (otherSharedTreeSelectionModel.getSelectedObject() != null) {
360                     deselectOthers(otherSharedTreeView, otherSharedTreeSelectionModel);
361                     applyPermissions(otherSharedTreeSelectionModel.getSelectedObject());
362                     updateOtherSharedFolder(otherSharedTreeSelectionModel.getSelectedObject(), true);
363                 }
364             }
365         });
366         selectionModels.add(otherSharedTreeSelectionModel);
367         otherSharedTreeViewModel = new OtherSharedTreeViewModel(this, otherSharedTreeSelectionModel);
368         otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel);
369
370         groupTreeViewModel = new GroupTreeViewModel(this);
371         groupTreeView = new GroupTreeView(groupTreeViewModel);
372
373         VerticalPanel trees = new VerticalPanel();
374
375         upload = new Button("Upload File", new ClickHandler() {
376             @Override
377             public void onClick(@SuppressWarnings("unused") ClickEvent event) {
378                 new UploadFileCommand(Pithos.this, null, getSelection()).execute();
379             }
380         });
381         upload.addStyleName("pithos-uploadButton");
382         trees.add(upload);
383         
384         HorizontalPanel treeHeader = new HorizontalPanel();
385         treeHeader.addStyleName("pithos-treeHeader");
386         HorizontalPanel statistics = new HorizontalPanel();
387         statistics.add(new HTML("Total Objects:&nbsp;"));
388         totalFiles = new HTML();
389         statistics.add(totalFiles);
390         statistics.add(new HTML("&nbsp;|&nbsp;Used:&nbsp;"));
391         usedBytes = new HTML();
392         statistics.add(usedBytes);
393         statistics.add(new HTML("&nbsp;of&nbsp;"));
394         totalBytes = new HTML();
395         statistics.add(totalBytes);
396         statistics.add(new HTML("&nbsp;("));
397         usedPercent = new HTML();
398         statistics.add(usedPercent);
399         statistics.add(new HTML("%)"));
400         treeHeader.add(statistics);
401         trees.add(treeHeader);
402
403         trees.add(folderTreeView);
404         trees.add(mysharedTreeView);
405         trees.add(otherSharedTreeView);
406 //        trees.add(tagTreeView);
407         trees.add(groupTreeView);
408         // Add the left and right panels to the split panel.
409         splitPanel.setLeftWidget(trees);
410         splitPanel.setRightWidget(inner);
411         splitPanel.setSplitPosition("25%");
412         splitPanel.setSize("100%", "100%");
413         splitPanel.addStyleName("pithos-splitPanel");
414         outer.add(splitPanel);
415
416         statusPanel = new StatusPanel();
417         outer.add(statusPanel);
418
419
420         // Hook the window resize event, so that we can adjust the UI.
421         Window.addResizeHandler(this);
422         // Clear out the window's built-in margin, because we want to take
423         // advantage of the entire client area.
424         Window.setMargin("0px");
425         // Finally, add the outer panel to the RootPanel, so that it will be
426         // displayed.
427         RootPanel.get().add(outer);
428         // Call the window resized handler to get the initial sizes setup. Doing
429         // this in a deferred command causes it to occur after all widgets'
430         // sizes have been computed by the browser.
431         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
432
433             @Override
434             public void execute() {
435                 onWindowResized(Window.getClientHeight());
436             }
437         });
438
439         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
440             @Override
441             public void execute() {
442                 fetchAccount(new Command() {
443                                         
444                                         @Override
445                                         public void execute() {
446                                 if (!account.hasHomeContainer())
447                                     createHomeContainer(account, this);
448                                 else if (!account.hasTrashContainer())
449                                         createTrashContainer(this);
450                                 else {
451                                         for (Folder f : account.getContainers())
452                                                 if (f.getName().equals(Pithos.TRASH_CONTAINER)) {
453                                                         trash = f;
454                                                         break;
455                                                 }
456                                     folderTreeViewModel.initialize(account);
457                                     groupTreeViewModel.initialize();
458                                     showStatistics();
459                                 }
460                                         }
461                                 });
462             }
463         });
464     }
465
466     public void applyPermissions(Folder f) {
467         if (f != null) {
468                 if (f.isInTrash())
469                         upload.setEnabled(false);
470                 else {
471                         Boolean[] perms = f.getPermissions().get(username);
472                         if (f.getOwner().equals(username) || (perms != null && perms[1] != null && perms[1])) {
473                                 upload.setEnabled(true);
474                         }
475                         else
476                                 upload.setEnabled(false);
477                 }
478         }
479         else
480                 upload.setEnabled(false);
481         }
482
483         @SuppressWarnings({ "rawtypes", "unchecked" })
484         public void deselectOthers(TreeView _selectedTree, SingleSelectionModel model) {
485         selectedTree = _selectedTree;
486         for (SingleSelectionModel s : selectionModels)
487             if (!s.equals(model))
488                 s.setSelected(s.getSelectedObject(), false);
489     }
490
491     public void showFiles(Folder f) {
492         Set<File> files = f.getFiles();
493         showFiles(files);
494     }
495
496     public void showFiles(Set<File> files) {
497         //Iterator<File> iter = files.iterator();
498         //fetchFile(iter, files);
499         fileList.setFiles(new ArrayList<File>(files));
500     }
501
502     protected void fetchFile(final Iterator<File> iter, final Set<File> files) {
503         if (iter.hasNext()) {
504             File file = iter.next();
505             String path = file.getUri() + "?format=json";
506             GetRequest<File> getFile = new GetRequest<File>(File.class, getApiPath(), username, path, file) {
507                 @Override
508                 public void onSuccess(@SuppressWarnings("unused") File _result) {
509                     fetchFile(iter, files);
510                 }
511
512                 @Override
513                 public void onError(Throwable t) {
514                     GWT.log("Error getting file", t);
515                     if (t instanceof RestException)
516                         displayError("Error getting file: " + ((RestException) t).getHttpStatusText());
517                     else
518                         displayError("System error fetching file: " + t.getMessage());
519                 }
520             };
521             getFile.setHeader("X-Auth-Token", "0000");
522             Scheduler.get().scheduleDeferred(getFile);
523         }
524         else
525             fileList.setFiles(new ArrayList<File>(files));
526     }
527
528     /**
529          * Parse and store the user credentials to the appropriate fields.
530          */
531         private boolean parseUserCredentials() {
532         username = Window.Location.getParameter("user");
533         token = Window.Location.getParameter("token");
534         Configuration conf = (Configuration) GWT.create(Configuration.class);
535         if (username == null || username.length() == 0 || token == null || token.length() == 0) {
536             String cookie = conf.authCookie();
537             String auth = Cookies.getCookie(cookie);
538             if (auth == null) {
539                 authenticateUser();
540                 return false;
541             }
542                         String[] authSplit = auth.split("\\" + conf.cookieSeparator(), 2);
543                         if (authSplit.length != 2) {
544                             authenticateUser();
545                             return false;
546                         }
547                         username = authSplit[0];
548                         token = authSplit[1];
549                         return true;
550         }
551                 Cookies.setCookie(conf.authCookie(), username + conf.cookieSeparator() + token);
552                 return true;
553     }
554
555     /**
556          * Redirect the user to the login page for authentication.
557          */
558         protected void authenticateUser() {
559                 Configuration conf = (Configuration) GWT.create(Configuration.class);
560         Window.Location.assign(conf.loginUrl() + "?next=" + Window.Location.getHref());
561         }
562
563         protected void fetchAccount(final Command callback) {
564         String path = "?format=json";
565
566         GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getApiPath(), username, path) {
567             @Override
568             public void onSuccess(AccountResource _result) {
569                 account = _result;
570                 if (callback != null)
571                         callback.execute();
572             }
573
574             @Override
575             public void onError(Throwable t) {
576                 GWT.log("Error getting account", t);
577                 if (t instanceof RestException)
578                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
579                 else
580                     displayError("System error fetching user data: " + t.getMessage());
581             }
582         };
583         getAccount.setHeader("X-Auth-Token", token);
584         Scheduler.get().scheduleDeferred(getAccount);
585     }
586
587     public void updateStatistics() {
588         HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getApiPath(), username, "", account) {
589
590                         @Override
591                         public void onSuccess(@SuppressWarnings("unused") AccountResource _result) {
592                                 showStatistics();
593                         }
594
595                         @Override
596                         public void onError(Throwable t) {
597                 GWT.log("Error getting account", t);
598                 if (t instanceof RestException)
599                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
600                 else
601                     displayError("System error fetching user data: " + t.getMessage());
602                         }
603                 };
604                 headAccount.setHeader("X-Auth-Token", token);
605                 Scheduler.get().scheduleDeferred(headAccount);
606         }
607
608         protected void showStatistics() {
609         totalFiles.setHTML(String.valueOf(account.getNumberOfObjects()));
610         usedBytes.setHTML(String.valueOf(account.getFileSizeAsString()));
611         totalBytes.setHTML(String.valueOf(account.getQuotaAsString()));
612         usedPercent.setHTML(String.valueOf(account.getUsedPercentage()));
613         }
614
615         protected void createHomeContainer(final AccountResource _account, final Command callback) {
616         String path = "/" + Pithos.HOME_CONTAINER;
617         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
618             @Override
619             public void onSuccess(@SuppressWarnings("unused") Resource result) {
620                 if (!_account.hasTrashContainer())
621                         createTrashContainer(callback);
622                 else
623                         fetchAccount(callback);
624             }
625
626             @Override
627             public void onError(Throwable t) {
628                 GWT.log("Error creating pithos", t);
629                 if (t instanceof RestException)
630                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
631                 else
632                     displayError("System error Error creating pithos: " + t.getMessage());
633             }
634         };
635         createPithos.setHeader("X-Auth-Token", getToken());
636         Scheduler.get().scheduleDeferred(createPithos);
637     }
638
639     protected void createTrashContainer(final Command callback) {
640         String path = "/" + Pithos.TRASH_CONTAINER;
641         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
642             @Override
643             public void onSuccess(@SuppressWarnings("unused") Resource result) {
644                         fetchAccount(callback);
645             }
646
647             @Override
648             public void onError(Throwable t) {
649                 GWT.log("Error creating pithos", t);
650                 if (t instanceof RestException)
651                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
652                 else
653                     displayError("System error Error creating pithos: " + t.getMessage());
654             }
655         };
656         createPithos.setHeader("X-Auth-Token", getToken());
657         Scheduler.get().scheduleDeferred(createPithos);
658     }
659
660     /**
661          * Creates an HTML fragment that places an image & caption together, for use
662          * in a group header.
663          *
664          * @param imageProto an image prototype for an image
665          * @param caption the group caption
666          * @return the header HTML fragment
667          */
668         private String createHeaderHTML(AbstractImagePrototype imageProto, String caption) {
669                 String captionHTML = "<table class='caption' cellpadding='0' " 
670                 + "cellspacing='0'>" + "<tr><td class='lcaption'>" + imageProto.getHTML() 
671                 + "</td><td id =" + caption +" class='rcaption'><b style='white-space:nowrap'>&nbsp;" 
672                 + caption + "</b></td></tr></table>";
673                 return captionHTML;
674         }
675
676         protected void onWindowResized(int height) {
677                 // Adjust the split panel to take up the available room in the window.
678                 int newHeight = height - splitPanel.getAbsoluteTop() - 60;
679                 if (newHeight < 1)
680                         newHeight = 1;
681                 splitPanel.setHeight("" + newHeight);
682                 inner.setHeight("" + newHeight);
683         }
684
685         @Override
686         public void onResize(ResizeEvent event) {
687                 int height = event.getHeight();
688                 onWindowResized(height);
689         }
690
691         /**
692          * Display an error message.
693          *
694          * @param msg the message to display
695          */
696         public void displayError(String msg) {
697                 messagePanel.displayError(msg);
698         }
699
700         /**
701          * Display a warning message.
702          *
703          * @param msg the message to display
704          */
705         public void displayWarning(String msg) {
706                 messagePanel.displayWarning(msg);
707         }
708
709         /**
710          * Display an informational message.
711          *
712          * @param msg the message to display
713          */
714         public void displayInformation(String msg) {
715                 messagePanel.displayInformation(msg);
716         }
717
718         /**
719          * Retrieve the fileList.
720          *
721          * @return the fileList
722          */
723         public FileList getFileList() {
724                 return fileList;
725         }
726
727         /**
728          * Retrieve the topPanel.
729          *
730          * @return the topPanel
731          */
732         TopPanel getTopPanel() {
733                 return topPanel;
734         }
735
736         /**
737          * Retrieve the clipboard.
738          *
739          * @return the clipboard
740          */
741         public Clipboard getClipboard() {
742                 return clipboard;
743         }
744
745         public StatusPanel getStatusPanel() {
746                 return statusPanel;
747         }
748
749         public String getToken() {
750                 return token;
751         }
752
753         public String getWebDAVPassword() {
754                 return webDAVPassword;
755         }
756
757         public static native void preventIESelection() /*-{
758                 $doc.body.onselectstart = function () { return false; };
759         }-*/;
760
761         public static native void enableIESelection() /*-{
762                 if ($doc.body.onselectstart != null)
763                 $doc.body.onselectstart = null;
764         }-*/;
765
766         /**
767          * @return the absolute path of the API root URL
768          */
769         public String getApiPath() {
770                 Configuration conf = (Configuration) GWT.create(Configuration.class);
771                 return conf.apiPath();
772         }
773
774         /**
775          * History support for folder navigation
776          * adds a new browser history entry
777          *
778          * @param key
779          */
780         public void updateHistory(String key){
781 //              Replace any whitespace of the initial string to "+"
782 //              String result = key.replaceAll("\\s","+");
783 //              Add a new browser history entry.
784 //              History.newItem(result);
785                 History.newItem(key);
786         }
787
788     public void deleteFolder(final Folder folder) {
789         String path = getApiPath() + folder.getOwner() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + folder.getPrefix();
790         RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
791         builder.setHeader("If-Modified-Since", "0");
792         builder.setHeader("X-Auth-Token", getToken());
793         try {
794             builder.sendRequest("", new RequestCallback() {
795                 @Override
796                 public void onResponseReceived(@SuppressWarnings("unused") Request request, Response response) {
797                     if (response.getStatusCode() == Response.SC_OK) {
798                         JSONValue json = JSONParser.parseStrict(response.getText());
799                         JSONArray array = json.isArray();
800                         int i = 0;
801                         if (array != null) {
802                             deleteObject(folder, i, array);
803                         }
804                     }
805                 }
806
807                 @Override
808                 public void onError(@SuppressWarnings("unused") Request request, Throwable exception) {
809                     displayError("System error unable to delete folder: " + exception.getMessage());
810                 }
811             });
812         }
813         catch (RequestException e) {
814         }
815     }
816
817     void deleteObject(final Folder folder, final int i, final JSONArray array) {
818         if (i < array.size()) {
819             JSONObject o = array.get(i).isObject();
820             if (o != null && !o.containsKey("subdir")) {
821                 JSONString name = o.get("name").isString();
822                 String path = "/" + folder.getContainer() + "/" + name.stringValue();
823                 DeleteRequest delete = new DeleteRequest(getApiPath(), folder.getOwner(), path) {
824                     @Override
825                     public void onSuccess(@SuppressWarnings("unused") Resource result) {
826                         deleteObject(folder, i + 1, array);
827                     }
828
829                     @Override
830                     public void onError(Throwable t) {
831                         GWT.log("", t);
832                         displayError("System error unable to delete folder: " + t.getMessage());
833                     }
834                 };
835                 delete.setHeader("X-Auth-Token", getToken());
836                 Scheduler.get().scheduleDeferred(delete);
837             }
838             else if (o != null) {
839                 String subdir = o.get("subdir").isString().stringValue();
840                 subdir = subdir.substring(0, subdir.length() - 1);
841                 String path = getApiPath() + getUsername() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + subdir;
842                 RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
843                 builder.setHeader("If-Modified-Since", "0");
844                 builder.setHeader("X-Auth-Token", getToken());
845                 try {
846                     builder.sendRequest("", new RequestCallback() {
847                         @Override
848                         public void onResponseReceived(@SuppressWarnings("unused") Request request, Response response) {
849                             if (response.getStatusCode() == Response.SC_OK) {
850                                 JSONValue json = JSONParser.parseStrict(response.getText());
851                                 JSONArray array2 = json.isArray();
852                                 if (array2 != null) {
853                                     int l = array.size();
854                                     for (int j=0; j<array2.size(); j++) {
855                                         array.set(l++, array2.get(j));
856                                     }
857                                 }
858                                 deleteObject(folder, i + 1, array);
859                             }
860                         }
861
862                         @Override
863                         public void onError(@SuppressWarnings("unused") Request request, Throwable exception) {
864                             displayError("System error unable to delete folder: " + exception.getMessage());
865                         }
866                     });
867                 }
868                 catch (RequestException e) {
869                 }
870             }
871         }
872         else {
873             String path = folder.getUri();
874             DeleteRequest deleteFolder = new DeleteRequest(getApiPath(), getUsername(), path) {
875                 @Override
876                 public void onSuccess(@SuppressWarnings("unused") Resource result) {
877                     updateFolder(folder.getParent(), true, new Command() {
878                                                 
879                                                 @Override
880                                                 public void execute() {
881                                                         updateStatistics();
882                                                 }
883                                         });
884                 }
885
886                 @Override
887                 public void onError(Throwable t) {
888                     GWT.log("", t);
889                     if (t instanceof RestException) {
890                         if (((RestException) t).getHttpStatusCode() != Response.SC_NOT_FOUND)
891                                 displayError("Unable to delete folder: "+((RestException) t).getHttpStatusText());
892                         else
893                                 onSuccess(null);
894                     }
895                     else
896                         displayError("System error unable to delete folder: " + t.getMessage());
897                 }
898             };
899             deleteFolder.setHeader("X-Auth-Token", getToken());
900             Scheduler.get().scheduleDeferred(deleteFolder);
901         }
902     }
903
904     public FolderTreeView getFolderTreeView() {
905         return folderTreeView;
906     }
907
908     public void copyFiles(final Iterator<File> iter, final String targetUsername, final String targetUri, final Command callback) {
909         if (iter.hasNext()) {
910             File file = iter.next();
911             String path = targetUri + "/" + file.getName();
912             PutRequest copyFile = new PutRequest(getApiPath(), targetUsername, path) {
913                 @Override
914                 public void onSuccess(@SuppressWarnings("unused") Resource result) {
915                     copyFiles(iter, targetUsername, targetUri, callback);
916                 }
917
918                 @Override
919                 public void onError(Throwable t) {
920                     GWT.log("", t);
921                     if (t instanceof RestException) {
922                         displayError("Unable to copy file: " + ((RestException) t).getHttpStatusText());
923                     }
924                     else
925                         displayError("System error unable to copy file: "+t.getMessage());
926                 }
927             };
928             copyFile.setHeader("X-Auth-Token", getToken());
929             copyFile.setHeader("X-Copy-From", file.getUri());
930             Scheduler.get().scheduleDeferred(copyFile);
931         }
932         else  if (callback != null) {
933             callback.execute();
934         }
935     }
936
937     public void copySubfolders(final Iterator<Folder> iter, final String targetUsername, final String targetUri, final Command callback) {
938         if (iter.hasNext()) {
939             final Folder f = iter.next();
940             copyFolder(f, targetUsername, targetUri, new Command() {
941                                 
942                                 @Override
943                                 public void execute() {
944                                         copySubfolders(iter, targetUsername, targetUri, callback);
945                                 }
946                         });
947         }
948         else  if (callback != null) {
949             callback.execute();
950         }
951     }
952
953     public void copyFolder(final Folder f, final String targetUsername, final String targetUri, final Command callback) {
954         String path = targetUri + "/" + f.getName();
955         PutRequest createFolder = new PutRequest(getApiPath(), targetUsername, path) {
956             @Override
957             public void onSuccess(@SuppressWarnings("unused") Resource result) {
958                 GetRequest<Folder> getFolder = new GetRequest<Folder>(Folder.class, getApiPath(), f.getOwner(), "/" + f.getContainer() + "?format=json&delimiter=/&prefix=" + f.getPrefix(), f) {
959
960                                         @Override
961                                         public void onSuccess(final Folder _f) {
962                                 Iterator<File> iter = _f.getFiles().iterator();
963                                 copyFiles(iter, targetUsername, targetUri + "/" + _f.getName(), new Command() {
964                                     @Override
965                                     public void execute() {
966                                         Iterator<Folder> iterf = _f.getSubfolders().iterator();
967                                         copySubfolders(iterf, targetUsername, targetUri + "/" + _f.getName(), callback);
968                                     }
969                                 });
970                                         }
971
972                                         @Override
973                                         public void onError(Throwable t) {
974                                 GWT.log("", t);
975                                 if (t instanceof RestException) {
976                                     displayError("Unable to get folder: " + ((RestException) t).getHttpStatusText());
977                                 }
978                                 else
979                                     displayError("System error getting folder: " + t.getMessage());
980                                         }
981                                 };
982                                 getFolder.setHeader("X-Auth-Token", getToken());
983                                 Scheduler.get().scheduleDeferred(getFolder);
984             }
985
986             @Override
987             public void onError(Throwable t) {
988                 GWT.log("", t);
989                 if (t instanceof RestException) {
990                     displayError("Unable to create folder: " + ((RestException) t).getHttpStatusText());
991                 }
992                 else
993                     displayError("System error creating folder: " + t.getMessage());
994             }
995         };
996         createFolder.setHeader("X-Auth-Token", getToken());
997         createFolder.setHeader("Accept", "*/*");
998         createFolder.setHeader("Content-Length", "0");
999         createFolder.setHeader("Content-Type", "application/folder");
1000         Scheduler.get().scheduleDeferred(createFolder);
1001     }
1002     
1003     public void addSelectionModel(@SuppressWarnings("rawtypes") SingleSelectionModel model) {
1004         selectionModels.add(model);
1005     }
1006
1007         public OtherSharedTreeView getOtherSharedTreeView() {
1008                 return otherSharedTreeView;
1009         }
1010
1011         public void updateTrash(boolean showFiles, Command callback) {
1012                 updateFolder(trash, showFiles, callback);
1013         }
1014
1015         public void updateGroupsNode() {
1016                 groupTreeView.updateGroupNode(null);
1017         }
1018
1019         public void addGroup(String groupname) {
1020                 Group newGroup = new Group(groupname);
1021                 account.addGroup(newGroup);
1022                 groupTreeView.updateGroupNode(null);
1023         }
1024
1025         public void removeGroup(Group group) {
1026                 account.removeGroup(group);
1027                 updateGroupsNode();
1028         }
1029
1030         public TreeView getSelectedTree() {
1031                 return selectedTree;
1032         }
1033         
1034         public Folder getSelection() {
1035                 return selectedTree.getSelection();
1036         }
1037
1038         public void showFolderStatistics(int folderFileCount) {
1039                 numOfFiles.setHTML(String.valueOf(folderFileCount));
1040         }
1041 }