efc25d54c9b2ab1c952b9d73fae6f716d3a5a704
[pithos-web-client] / src / gr / grnet / pithos / web / client / Pithos.java
1 /*
2  * Copyright 2011-2012 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
57 import java.util.ArrayList;
58 import java.util.HashMap;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Set;
62
63 import com.google.gwt.core.client.EntryPoint;
64 import com.google.gwt.core.client.GWT;
65 import com.google.gwt.core.client.Scheduler;
66 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
67 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
68 import com.google.gwt.event.dom.client.ClickEvent;
69 import com.google.gwt.event.dom.client.ClickHandler;
70 import com.google.gwt.event.logical.shared.ResizeEvent;
71 import com.google.gwt.event.logical.shared.ResizeHandler;
72 import com.google.gwt.http.client.Request;
73 import com.google.gwt.http.client.RequestBuilder;
74 import com.google.gwt.http.client.RequestCallback;
75 import com.google.gwt.http.client.RequestException;
76 import com.google.gwt.http.client.Response;
77 import com.google.gwt.http.client.URL;
78 import com.google.gwt.i18n.client.Dictionary;
79 import com.google.gwt.i18n.client.NumberFormat;
80 import com.google.gwt.json.client.JSONArray;
81 import com.google.gwt.json.client.JSONObject;
82 import com.google.gwt.json.client.JSONParser;
83 import com.google.gwt.json.client.JSONString;
84 import com.google.gwt.json.client.JSONValue;
85 import com.google.gwt.resources.client.ImageResource;
86 import com.google.gwt.resources.client.ImageResource.ImageOptions;
87 import com.google.gwt.user.client.Command;
88 import com.google.gwt.user.client.Cookies;
89 import com.google.gwt.user.client.Event;
90 import com.google.gwt.user.client.History;
91 import com.google.gwt.user.client.Window;
92 import com.google.gwt.user.client.ui.AbstractImagePrototype;
93 import com.google.gwt.user.client.ui.Button;
94 import com.google.gwt.user.client.ui.Composite;
95 import com.google.gwt.user.client.ui.HTML;
96 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
97 import com.google.gwt.user.client.ui.HasVerticalAlignment;
98 import com.google.gwt.user.client.ui.HorizontalPanel;
99 import com.google.gwt.user.client.ui.HorizontalSplitPanel;
100 import com.google.gwt.user.client.ui.RootPanel;
101 import com.google.gwt.user.client.ui.VerticalPanel;
102 import com.google.gwt.view.client.SelectionChangeEvent;
103 import com.google.gwt.view.client.SelectionChangeEvent.Handler;
104 import com.google.gwt.view.client.SingleSelectionModel;
105
106 /**
107  * Entry point classes define <code>onModuleLoad()</code>.
108  */
109 public class Pithos implements EntryPoint, ResizeHandler {
110
111         public static final String HOME_CONTAINER = "pithos";
112
113         public static final String TRASH_CONTAINER = "trash";
114
115         public static final Configuration config = GWT.create(Configuration.class);
116         
117         /**
118          * Instantiate an application-level image bundle. This object will provide
119          * programmatic access to all the images needed by widgets.
120          */
121         static Images images = (Images) GWT.create(Images.class);
122
123     public String getUsername() {
124         return username;
125     }
126
127     public void setAccount(AccountResource acct) {
128         account = acct;
129     }
130
131     public AccountResource getAccount() {
132         return account;
133     }
134
135     public void updateFolder(Folder f, boolean showfiles, Command callback, final boolean openParent) {
136         folderTreeView.updateFolder(f, showfiles, callback, openParent);
137     }
138
139     public void updateGroupNode(Group group) {
140         groupTreeView.updateGroupNode(group);
141     }
142
143     public void updateMySharedRoot() {
144         mysharedTreeView.updateRoot();
145     }
146     
147     public void updateSharedFolder(Folder f, boolean showfiles) {
148         mysharedTreeView.updateFolder(f, showfiles);
149     }
150     
151     public void updateOtherSharedFolder(Folder f, boolean showfiles) {
152         otherSharedTreeView.updateFolder(f, showfiles);
153     }
154
155     public MysharedTreeView getMySharedTreeView() {
156         return mysharedTreeView;
157     }
158
159     /**
160          * An aggregate image bundle that pulls together all the images for this
161          * application into a single bundle.
162          */
163         public interface Images extends TopPanel.Images, FileList.Images, ToolsMenu.Images {
164
165                 @Source("gr/grnet/pithos/resources/document.png")
166                 ImageResource folders();
167
168                 @Source("gr/grnet/pithos/resources/advancedsettings.png")
169                 @ImageOptions(width=32, height=32)
170                 ImageResource tools();
171         }
172
173         private Throwable error;
174         
175         /**
176          * The Application Clipboard implementation;
177          */
178         private Clipboard clipboard = new Clipboard();
179
180         /**
181          * The top panel that contains the menu bar.
182          */
183         private TopPanel topPanel;
184
185         /**
186          * The panel that contains the various system messages.
187          */
188         private MessagePanel messagePanel = new MessagePanel(this, Pithos.images);
189
190         /**
191          * The bottom panel that contains the status bar.
192          */
193         private StatusPanel statusPanel = null;
194
195         /**
196          * The file list widget.
197          */
198         private FileList fileList;
199
200         /**
201          * The tab panel that occupies the right side of the screen.
202          */
203         private VerticalPanel inner = new VerticalPanel();
204
205
206         /**
207          * The split panel that will contain the left and right panels.
208          */
209         private HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
210
211         /**
212          * The currently selected item in the application, for use by the Edit menu
213          * commands. Potential types are Folder, File, User and Group.
214          */
215         private Object currentSelection;
216
217         public HashMap<String, String> userFullNameMap = new HashMap<String, String>();
218
219     private String username = null;
220
221     /**
222      * The authentication token of the current user.
223      */
224     private String token;
225
226     VerticalPanel trees;
227     
228     SingleSelectionModel<Folder> folderTreeSelectionModel;
229     FolderTreeViewModel folderTreeViewModel;
230     FolderTreeView folderTreeView;
231
232     SingleSelectionModel<Folder> mysharedTreeSelectionModel;
233     MysharedTreeViewModel mysharedTreeViewModel;
234     MysharedTreeView mysharedTreeView = null;;
235
236     protected SingleSelectionModel<Folder> otherSharedTreeSelectionModel;
237     OtherSharedTreeViewModel otherSharedTreeViewModel;
238     OtherSharedTreeView otherSharedTreeView = null;
239
240     GroupTreeViewModel groupTreeViewModel;
241     GroupTreeView groupTreeView;
242
243     TreeView selectedTree;
244     protected AccountResource account;
245     
246     Folder trash;
247     
248     List<Composite> treeViews = new ArrayList<Composite>();
249
250     @SuppressWarnings("rawtypes") List<SingleSelectionModel> selectionModels = new ArrayList<SingleSelectionModel>();
251     
252     Button upload;
253     
254     private HTML usedBytes;
255     
256     private HTML totalBytes;
257     
258     private HTML usedPercent;
259     
260     private HTML numOfFiles;
261     
262     private Toolbar toolbar;
263     
264     private FileUploadDialog fileUploadDialog;
265     
266         @Override
267         public void onModuleLoad() {
268                 if (parseUserCredentials())
269             initialize();
270         }
271
272     private void initialize() {
273         boolean bareContent = Window.Location.getParameter("noframe") != null;
274         String contentWidth = bareContent ? "100%" : "75%";
275
276         VerticalPanel outer = new VerticalPanel();
277         outer.setWidth("100%");
278         if (!bareContent) {
279                 outer.addStyleName("pithos-outer");
280         }
281
282         if (!bareContent) {
283                 topPanel = new TopPanel(this, Pithos.images);
284                 topPanel.setWidth("100%");
285                 outer.add(topPanel);
286                 outer.setCellHorizontalAlignment(topPanel, HasHorizontalAlignment.ALIGN_CENTER);
287         }
288         
289         messagePanel.setVisible(false);
290         outer.add(messagePanel);
291         outer.setCellHorizontalAlignment(messagePanel, HasHorizontalAlignment.ALIGN_CENTER);
292         outer.setCellVerticalAlignment(messagePanel, HasVerticalAlignment.ALIGN_MIDDLE);
293
294         HorizontalPanel header = new HorizontalPanel();
295         header.addStyleName("pithos-header");
296         header.setWidth(contentWidth);
297         if (bareContent)
298                 header.addStyleName("pithos-header-noframe");
299         upload = new Button("Upload", new ClickHandler() {
300             @Override
301             public void onClick(ClickEvent event) {
302                 if (getSelection() != null)
303                         new UploadFileCommand(Pithos.this, null, getSelection()).execute();
304             }
305         });
306         upload.addStyleName("pithos-uploadButton");
307         header.add(upload);
308         header.setCellHorizontalAlignment(upload, HasHorizontalAlignment.ALIGN_LEFT);
309         header.setCellVerticalAlignment(upload, HasVerticalAlignment.ALIGN_MIDDLE);
310
311         toolbar = new Toolbar(this);
312         header.add(toolbar);
313         header.setCellHorizontalAlignment(toolbar, HasHorizontalAlignment.ALIGN_CENTER);
314         header.setCellVerticalAlignment(toolbar, HasVerticalAlignment.ALIGN_MIDDLE);
315         
316         HorizontalPanel folderStatistics = new HorizontalPanel();
317         folderStatistics.addStyleName("pithos-folderStatistics");
318         numOfFiles = new HTML();
319         folderStatistics.add(numOfFiles);
320         folderStatistics.setCellVerticalAlignment(numOfFiles, HasVerticalAlignment.ALIGN_MIDDLE);
321         HTML numOfFilesLabel = new HTML("&nbsp;Files");
322         folderStatistics.add(numOfFilesLabel);
323         folderStatistics.setCellVerticalAlignment(numOfFilesLabel, HasVerticalAlignment.ALIGN_MIDDLE);
324         header.add(folderStatistics);
325         header.setCellHorizontalAlignment(folderStatistics, HasHorizontalAlignment.ALIGN_RIGHT);
326         header.setCellVerticalAlignment(folderStatistics, HasVerticalAlignment.ALIGN_MIDDLE);
327         header.setCellWidth(folderStatistics, "40px");
328         outer.add(header);
329         outer.setCellHorizontalAlignment(header, HasHorizontalAlignment.ALIGN_CENTER);
330         // Inner contains the various lists.nner
331         inner.sinkEvents(Event.ONCONTEXTMENU);
332         inner.setWidth("100%");
333
334         folderTreeSelectionModel = new SingleSelectionModel<Folder>();
335         folderTreeSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
336             @Override
337             public void onSelectionChange(SelectionChangeEvent event) {
338                 if (folderTreeSelectionModel.getSelectedObject() != null) {
339                     deselectOthers(folderTreeView, folderTreeSelectionModel);
340                     applyPermissions(folderTreeSelectionModel.getSelectedObject());
341                     Folder f = folderTreeSelectionModel.getSelectedObject();
342                         updateFolder(f, true, new Command() {
343                                 
344                                 @Override
345                                 public void execute() {
346                                         updateStatistics();
347                                 }
348                         }, true);
349                         showRelevantToolbarButtons();
350                 }
351                                 else {
352                                         if (getSelectedTree().equals(folderTreeView))
353                                                 setSelectedTree(null);
354                                         if (getSelectedTree() == null)
355                                                 showRelevantToolbarButtons();
356                                 }
357             }
358         });
359         selectionModels.add(folderTreeSelectionModel);
360
361         folderTreeViewModel = new FolderTreeViewModel(this, folderTreeSelectionModel);
362         folderTreeView = new FolderTreeView(folderTreeViewModel);
363         treeViews.add(folderTreeView);
364         
365         fileList = new FileList(this, images, folderTreeView);
366         inner.add(fileList);
367
368         trees = new VerticalPanel();
369         trees.setWidth("100%");
370         
371         trees.add(folderTreeView);
372         
373         HorizontalPanel separator = new HorizontalPanel();
374         separator.addStyleName("pithos-statisticsSeparator");
375         trees.add(separator);
376         
377         HorizontalPanel statistics = new HorizontalPanel();
378             statistics.addStyleName("pithos-statistics");
379             statistics.add(new HTML("Used:&nbsp;"));
380             usedBytes = new HTML();
381             statistics.add(usedBytes);
382             statistics.add(new HTML("&nbsp;of&nbsp;"));
383             totalBytes = new HTML();
384             statistics.add(totalBytes);
385             statistics.add(new HTML("&nbsp;("));
386             usedPercent = new HTML();
387             statistics.add(usedPercent);
388             statistics.add(new HTML(")"));
389         trees.add(statistics);
390         
391         // Add the left and right panels to the split panel.
392         splitPanel.setLeftWidget(trees);
393         splitPanel.setRightWidget(inner);
394         splitPanel.setSplitPosition("219px");
395         splitPanel.setSize("100%", "100%");
396         splitPanel.addStyleName("pithos-splitPanel");
397         splitPanel.setWidth(contentWidth);
398         outer.add(splitPanel);
399         outer.setCellHorizontalAlignment(splitPanel, HasHorizontalAlignment.ALIGN_CENTER);
400
401         if (!bareContent) {
402                 statusPanel = new StatusPanel();
403                 statusPanel.setWidth("100%");
404                 outer.add(statusPanel);
405                 outer.setCellHorizontalAlignment(statusPanel, HasHorizontalAlignment.ALIGN_CENTER);
406         }
407         else
408                 splitPanel.addStyleName("pithos-splitPanel-noframe");
409
410         // Hook the window resize event, so that we can adjust the UI.
411         Window.addResizeHandler(this);
412         // Clear out the window's built-in margin, because we want to take
413         // advantage of the entire client area.
414         Window.setMargin("0px");
415         // Finally, add the outer panel to the RootPanel, so that it will be
416         // displayed.
417         RootPanel.get().add(outer);
418         // Call the window resized handler to get the initial sizes setup. Doing
419         // this in a deferred command causes it to occur after all widgets'
420         // sizes have been computed by the browser.
421         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
422
423             @Override
424             public void execute() {
425                 onWindowResized(Window.getClientHeight());
426             }
427         });
428
429         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
430             @Override
431             public void execute() {
432                 fetchAccount(new Command() {
433                                         
434                                         @Override
435                                         public void execute() {
436                                 if (!account.hasHomeContainer())
437                                     createHomeContainer(account, this);
438                                 else if (!account.hasTrashContainer())
439                                         createTrashContainer(this);
440                                 else {
441                                         for (Folder f : account.getContainers())
442                                                 if (f.getName().equals(Pithos.TRASH_CONTAINER)) {
443                                                         trash = f;
444                                                         break;
445                                                 }
446                                     folderTreeViewModel.initialize(account, new Command() {
447                                                                 
448                                                                 @Override
449                                                                 public void execute() {
450                                                     createMySharedTree();
451                                                                 }
452                                                         });
453                                     groupTreeViewModel = new GroupTreeViewModel(Pithos.this);
454                                     groupTreeView = new GroupTreeView(groupTreeViewModel);
455                                     treeViews.add(groupTreeView);
456                                     trees.add(groupTreeView);
457                                     showStatistics();
458                                 }
459                                         }
460                                 });
461             }
462         });
463         
464         Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
465                         
466                         @Override
467                         public boolean execute() {
468                                 Folder f = getSelection();
469                                 if (f != null) {
470                                         if (getSelectedTree().equals(folderTreeView))
471                                                 updateFolder(f, true, null, false);
472                                         else if (getSelectedTree().equals(mysharedTreeView))
473                                                 updateSharedFolder(f, true);
474                                 }
475                                 return true;
476                         }
477                 }, 3000);
478     }
479
480     public void applyPermissions(Folder f) {
481         if (f != null) {
482                 if (f.isInTrash())
483                         upload.setEnabled(false);
484                 else {
485                         Boolean[] perms = f.getPermissions().get(username);
486                         if (f.getOwner().equals(username) || (perms != null && perms[1] != null && perms[1])) {
487                                 upload.setEnabled(true);
488                         }
489                         else
490                                 upload.setEnabled(false);
491                 }
492         }
493         else
494                 upload.setEnabled(false);
495         }
496
497         @SuppressWarnings({ "rawtypes", "unchecked" })
498         public void deselectOthers(TreeView _selectedTree, SingleSelectionModel model) {
499         selectedTree = _selectedTree;
500         
501         for (Composite c : treeViews)
502                 if (c.equals(selectedTree))
503                         c.addStyleName("cellTreeWidget-selectedTree");
504                 else
505                         c.removeStyleName("cellTreeWidget-selectedTree");
506         
507         for (SingleSelectionModel s : selectionModels)
508             if (!s.equals(model) && s.getSelectedObject() != null)
509                 s.setSelected(s.getSelectedObject(), false);
510     }
511
512     public void showFiles(final Folder f) {
513         Set<File> files = f.getFiles();
514         showFiles(files);
515     }
516
517     public void showFiles(Set<File> files) {
518         fileList.setFiles(new ArrayList<File>(files));
519     }
520
521     /**
522          * Parse and store the user credentials to the appropriate fields.
523          */
524         private boolean parseUserCredentials() {
525         Configuration conf = (Configuration) GWT.create(Configuration.class);
526                 Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
527         String cookie = otherProperties.get("authCookie");
528         String auth = Cookies.getCookie(cookie);
529         if (auth == null) {
530             authenticateUser();
531             return false;
532         }
533         if (auth.startsWith("\""))
534                 auth = auth.substring(1);
535         if (auth.endsWith("\""))
536                 auth = auth.substring(0, auth.length() - 1);
537                 String[] authSplit = auth.split("\\" + conf.cookieSeparator(), 2);
538                 if (authSplit.length != 2) {
539                     authenticateUser();
540                     return false;
541                 }
542                 username = authSplit[0];
543                 token = authSplit[1];
544
545         String gotoUrl = Window.Location.getParameter("goto");
546                 if (gotoUrl != null && gotoUrl.length() > 0) {
547                         Window.Location.assign(gotoUrl);
548                         return false;
549                 }
550                 return true;
551     }
552
553     /**
554          * Redirect the user to the login page for authentication.
555          */
556         protected void authenticateUser() {
557                 Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
558         Window.Location.assign(otherProperties.get("loginUrl") + Window.Location.getHref());
559         }
560
561         protected void fetchAccount(final Command callback) {
562         String path = "?format=json";
563
564         GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getApiPath(), username, path) {
565             @Override
566             public void onSuccess(AccountResource _result) {
567                 account = _result;
568                 if (callback != null)
569                         callback.execute();
570             }
571
572             @Override
573             public void onError(Throwable t) {
574                 GWT.log("Error getting account", t);
575                                 setError(t);
576                 if (t instanceof RestException)
577                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
578                 else
579                     displayError("System error fetching user data: " + t.getMessage());
580             }
581
582                         @Override
583                         protected void onUnauthorized(Response response) {
584                                 sessionExpired();
585                         }
586         };
587         getAccount.setHeader("X-Auth-Token", token);
588         Scheduler.get().scheduleDeferred(getAccount);
589     }
590
591     public void updateStatistics() {
592         HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getApiPath(), username, "", account) {
593
594                         @Override
595                         public void onSuccess(AccountResource _result) {
596                                 showStatistics();
597                         }
598
599                         @Override
600                         public void onError(Throwable t) {
601                 GWT.log("Error getting account", t);
602                                 setError(t);
603                 if (t instanceof RestException)
604                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
605                 else
606                     displayError("System error fetching user data: " + t.getMessage());
607                         }
608
609                         @Override
610                         protected void onUnauthorized(Response response) {
611                                 sessionExpired();
612                         }
613                 };
614                 headAccount.setHeader("X-Auth-Token", token);
615                 Scheduler.get().scheduleDeferred(headAccount);
616         }
617
618         protected void showStatistics() {
619         usedBytes.setHTML(String.valueOf(account.getFileSizeAsString()));
620         totalBytes.setHTML(String.valueOf(account.getQuotaAsString()));
621         NumberFormat nf = NumberFormat.getPercentFormat();
622         usedPercent.setHTML(nf.format(account.getUsedPercentage()));
623         }
624
625         protected void createHomeContainer(final AccountResource _account, final Command callback) {
626         String path = "/" + Pithos.HOME_CONTAINER;
627         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
628             @Override
629             public void onSuccess(Resource result) {
630                 if (!_account.hasTrashContainer())
631                         createTrashContainer(callback);
632                 else
633                         fetchAccount(callback);
634             }
635
636             @Override
637             public void onError(Throwable t) {
638                 GWT.log("Error creating pithos", t);
639                                 setError(t);
640                 if (t instanceof RestException)
641                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
642                 else
643                     displayError("System error Error creating pithos: " + t.getMessage());
644             }
645
646                         @Override
647                         protected void onUnauthorized(Response response) {
648                                 sessionExpired();
649                         }
650         };
651         createPithos.setHeader("X-Auth-Token", getToken());
652         Scheduler.get().scheduleDeferred(createPithos);
653     }
654
655     protected void createTrashContainer(final Command callback) {
656         String path = "/" + Pithos.TRASH_CONTAINER;
657         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
658             @Override
659             public void onSuccess(Resource result) {
660                         fetchAccount(callback);
661             }
662
663             @Override
664             public void onError(Throwable t) {
665                 GWT.log("Error creating pithos", t);
666                                 setError(t);
667                 if (t instanceof RestException)
668                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
669                 else
670                     displayError("System error Error creating pithos: " + t.getMessage());
671             }
672
673                         @Override
674                         protected void onUnauthorized(Response response) {
675                                 sessionExpired();
676                         }
677         };
678         createPithos.setHeader("X-Auth-Token", getToken());
679         Scheduler.get().scheduleDeferred(createPithos);
680     }
681
682     /**
683          * Creates an HTML fragment that places an image & caption together, for use
684          * in a group header.
685          *
686          * @param imageProto an image prototype for an image
687          * @param caption the group caption
688          * @return the header HTML fragment
689          */
690         private String createHeaderHTML(AbstractImagePrototype imageProto, String caption) {
691                 String captionHTML = "<table class='caption' cellpadding='0' " 
692                 + "cellspacing='0'>" + "<tr><td class='lcaption'>" + imageProto.getHTML() 
693                 + "</td><td id =" + caption +" class='rcaption'><b style='white-space:nowrap'>&nbsp;" 
694                 + caption + "</b></td></tr></table>";
695                 return captionHTML;
696         }
697
698         protected void onWindowResized(int height) {
699                 // Adjust the split panel to take up the available room in the window.
700                 int newHeight = height - splitPanel.getAbsoluteTop();
701                 if (newHeight < 1)
702                         newHeight = 1;
703                 splitPanel.setHeight("" + newHeight);
704                 inner.setHeight("" + newHeight);
705         }
706
707         @Override
708         public void onResize(ResizeEvent event) {
709                 int height = event.getHeight();
710                 onWindowResized(height);
711         }
712
713         /**
714          * Display an error message.
715          *
716          * @param msg the message to display
717          */
718         public void displayError(String msg) {
719                 messagePanel.displayError(msg);
720         }
721
722         /**
723          * Display a warning message.
724          *
725          * @param msg the message to display
726          */
727         public void displayWarning(String msg) {
728                 messagePanel.displayWarning(msg);
729         }
730
731         /**
732          * Display an informational message.
733          *
734          * @param msg the message to display
735          */
736         public void displayInformation(String msg) {
737                 messagePanel.displayInformation(msg);
738         }
739
740         /**
741          * Retrieve the fileList.
742          *
743          * @return the fileList
744          */
745         public FileList getFileList() {
746                 return fileList;
747         }
748
749         /**
750          * Retrieve the topPanel.
751          *
752          * @return the topPanel
753          */
754         TopPanel getTopPanel() {
755                 return topPanel;
756         }
757
758         /**
759          * Retrieve the clipboard.
760          *
761          * @return the clipboard
762          */
763         public Clipboard getClipboard() {
764                 return clipboard;
765         }
766
767         public StatusPanel getStatusPanel() {
768                 return statusPanel;
769         }
770
771         public String getToken() {
772                 return token;
773         }
774
775         public static native void preventIESelection() /*-{
776                 $doc.body.onselectstart = function () { return false; };
777         }-*/;
778
779         public static native void enableIESelection() /*-{
780                 if ($doc.body.onselectstart != null)
781                 $doc.body.onselectstart = null;
782         }-*/;
783
784         /**
785          * @return the absolute path of the API root URL
786          */
787         public String getApiPath() {
788                 Configuration conf = (Configuration) GWT.create(Configuration.class);
789                 return conf.apiPath();
790         }
791
792         /**
793          * History support for folder navigation
794          * adds a new browser history entry
795          *
796          * @param key
797          */
798         public void updateHistory(String key){
799 //              Replace any whitespace of the initial string to "+"
800 //              String result = key.replaceAll("\\s","+");
801 //              Add a new browser history entry.
802 //              History.newItem(result);
803                 History.newItem(key);
804         }
805
806     public void deleteFolder(final Folder folder, final Command callback) {
807         String path = getApiPath() + folder.getOwner() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(folder.getPrefix()) + "&t=" + System.currentTimeMillis();
808         RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
809         builder.setHeader("X-Auth-Token", getToken());
810         try {
811             builder.sendRequest("", new RequestCallback() {
812                 @Override
813                 public void onResponseReceived(Request request, Response response) {
814                     if (response.getStatusCode() == Response.SC_OK) {
815                         JSONValue json = JSONParser.parseStrict(response.getText());
816                         JSONArray array = json.isArray();
817                         int i = 0;
818                         if (array != null) {
819                             deleteObject(folder, i, array, callback);
820                         }
821                     }
822                 }
823
824                 @Override
825                 public void onError(Request request, Throwable exception) {
826                         setError(exception);
827                     displayError("System error unable to delete folder: " + exception.getMessage());
828                 }
829             });
830         }
831         catch (RequestException e) {
832         }
833     }
834
835     void deleteObject(final Folder folder, final int i, final JSONArray array, final Command callback) {
836         if (i < array.size()) {
837             JSONObject o = array.get(i).isObject();
838             if (o != null && !o.containsKey("subdir")) {
839                 JSONString name = o.get("name").isString();
840                 String path = "/" + folder.getContainer() + "/" + name.stringValue();
841                 DeleteRequest delete = new DeleteRequest(getApiPath(), folder.getOwner(), URL.encode(path)) {
842                     @Override
843                     public void onSuccess(Resource result) {
844                         deleteObject(folder, i + 1, array, callback);
845                     }
846
847                     @Override
848                     public void onError(Throwable t) {
849                         GWT.log("", t);
850                                                 setError(t);
851                         displayError("System error unable to delete folder: " + t.getMessage());
852                     }
853
854                                 @Override
855                                 protected void onUnauthorized(Response response) {
856                                         sessionExpired();
857                                 }
858                 };
859                 delete.setHeader("X-Auth-Token", getToken());
860                 Scheduler.get().scheduleDeferred(delete);
861             }
862             else if (o != null) {
863                 String subdir = o.get("subdir").isString().stringValue();
864                 subdir = subdir.substring(0, subdir.length() - 1);
865                 String path = getApiPath() + getUsername() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(subdir) + "&t=" + System.currentTimeMillis();
866                 RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
867                 builder.setHeader("X-Auth-Token", getToken());
868                 try {
869                     builder.sendRequest("", new RequestCallback() {
870                         @Override
871                         public void onResponseReceived(Request request, Response response) {
872                             if (response.getStatusCode() == Response.SC_OK) {
873                                 JSONValue json = JSONParser.parseStrict(response.getText());
874                                 JSONArray array2 = json.isArray();
875                                 if (array2 != null) {
876                                     int l = array.size();
877                                     for (int j=0; j<array2.size(); j++) {
878                                         array.set(l++, array2.get(j));
879                                     }
880                                 }
881                                 deleteObject(folder, i + 1, array, callback);
882                             }
883                         }
884
885                         @Override
886                         public void onError(Request request, Throwable exception) {
887                                 setError(exception);
888                             displayError("System error unable to delete folder: " + exception.getMessage());
889                         }
890                     });
891                 }
892                 catch (RequestException e) {
893                 }
894             }
895         }
896         else {
897             String path = folder.getUri();
898             DeleteRequest deleteFolder = new DeleteRequest(getApiPath(), getUsername(), URL.encode(path)) {
899                 @Override
900                 public void onSuccess(Resource result) {
901                     updateFolder(folder.getParent(), true, new Command() {
902                                                 
903                                                 @Override
904                                                 public void execute() {
905                                                         folderTreeSelectionModel.setSelected(folder.getParent(), true);
906                                                         updateStatistics();
907                                                         if (callback != null)
908                                                                 callback.execute();
909                                                 }
910                                         }, true);
911                 }
912
913                 @Override
914                 public void onError(Throwable t) {
915                     GWT.log("", t);
916                                         setError(t);
917                     if (t instanceof RestException) {
918                         if (((RestException) t).getHttpStatusCode() != Response.SC_NOT_FOUND)
919                                 displayError("Unable to delete folder: "+((RestException) t).getHttpStatusText());
920                         else
921                                 onSuccess(null);
922                     }
923                     else
924                         displayError("System error unable to delete folder: " + t.getMessage());
925                 }
926
927                                 @Override
928                                 protected void onUnauthorized(Response response) {
929                                         sessionExpired();
930                                 }
931             };
932             deleteFolder.setHeader("X-Auth-Token", getToken());
933             Scheduler.get().scheduleDeferred(deleteFolder);
934         }
935     }
936
937     public FolderTreeView getFolderTreeView() {
938         return folderTreeView;
939     }
940
941     public void copyFiles(final Iterator<File> iter, final String targetUsername, final String targetUri, final Command callback) {
942         if (iter.hasNext()) {
943             File file = iter.next();
944             String path = targetUri + "/" + file.getName();
945             PutRequest copyFile = new PutRequest(getApiPath(), targetUsername, path) {
946                 @Override
947                 public void onSuccess(Resource result) {
948                     copyFiles(iter, targetUsername, targetUri, callback);
949                 }
950
951                 @Override
952                 public void onError(Throwable t) {
953                     GWT.log("", t);
954                                         setError(t);
955                     if (t instanceof RestException) {
956                         displayError("Unable to copy file: " + ((RestException) t).getHttpStatusText());
957                     }
958                     else
959                         displayError("System error unable to copy file: "+t.getMessage());
960                 }
961
962                                 @Override
963                                 protected void onUnauthorized(Response response) {
964                                         sessionExpired();
965                                 }
966             };
967             copyFile.setHeader("X-Auth-Token", getToken());
968             copyFile.setHeader("X-Copy-From", URL.encodePathSegment(file.getUri()));
969             if (!file.getOwner().equals(targetUsername))
970                 copyFile.setHeader("X-Source-Account", URL.encodePathSegment(file.getOwner()));
971             copyFile.setHeader("Content-Type", file.getContentType());
972             Scheduler.get().scheduleDeferred(copyFile);
973         }
974         else  if (callback != null) {
975             callback.execute();
976         }
977     }
978
979     public void copySubfolders(final Iterator<Folder> iter, final String targetUsername, final String targetUri, final Command callback) {
980         if (iter.hasNext()) {
981             final Folder f = iter.next();
982             copyFolder(f, targetUsername, targetUri, new Command() {
983                                 
984                                 @Override
985                                 public void execute() {
986                                         copySubfolders(iter, targetUsername, targetUri, callback);
987                                 }
988                         });
989         }
990         else  if (callback != null) {
991             callback.execute();
992         }
993     }
994
995     public void copyFolder(final Folder f, final String targetUsername, final String targetUri, final Command callback) {
996         String path = targetUri + "/" + f.getName();
997         PutRequest createFolder = new PutRequest(getApiPath(), targetUsername, path) {
998             @Override
999             public void onSuccess(Resource result) {
1000                 GetRequest<Folder> getFolder = new GetRequest<Folder>(Folder.class, getApiPath(), f.getOwner(), "/" + f.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(f.getPrefix()), f) {
1001
1002                                         @Override
1003                                         public void onSuccess(final Folder _f) {
1004                                 Iterator<File> iter = _f.getFiles().iterator();
1005                                 copyFiles(iter, targetUsername, targetUri + "/" + _f.getName(), new Command() {
1006                                     @Override
1007                                     public void execute() {
1008                                         Iterator<Folder> iterf = _f.getSubfolders().iterator();
1009                                         copySubfolders(iterf, targetUsername, targetUri + "/" + _f.getName(), callback);
1010                                     }
1011                                 });
1012                                         }
1013
1014                                         @Override
1015                                         public void onError(Throwable t) {
1016                                 GWT.log("", t);
1017                                                 setError(t);
1018                                 if (t instanceof RestException) {
1019                                     displayError("Unable to get folder: " + ((RestException) t).getHttpStatusText());
1020                                 }
1021                                 else
1022                                     displayError("System error getting folder: " + t.getMessage());
1023                                         }
1024
1025                                         @Override
1026                                         protected void onUnauthorized(Response response) {
1027                                                 sessionExpired();
1028                                         }
1029                                 };
1030                                 getFolder.setHeader("X-Auth-Token", getToken());
1031                                 Scheduler.get().scheduleDeferred(getFolder);
1032             }
1033
1034             @Override
1035             public void onError(Throwable t) {
1036                 GWT.log("", t);
1037                                 setError(t);
1038                if (t instanceof RestException) {
1039                     displayError("Unable to create folder: " + ((RestException) t).getHttpStatusText());
1040                 }
1041                 else
1042                     displayError("System error creating folder: " + t.getMessage());
1043             }
1044
1045                         @Override
1046                         protected void onUnauthorized(Response response) {
1047                                 sessionExpired();
1048                         }
1049         };
1050         createFolder.setHeader("X-Auth-Token", getToken());
1051         createFolder.setHeader("Accept", "*/*");
1052         createFolder.setHeader("Content-Length", "0");
1053         createFolder.setHeader("Content-Type", "application/folder");
1054         Scheduler.get().scheduleDeferred(createFolder);
1055     }
1056     
1057     public void addSelectionModel(@SuppressWarnings("rawtypes") SingleSelectionModel model) {
1058         selectionModels.add(model);
1059     }
1060
1061         public OtherSharedTreeView getOtherSharedTreeView() {
1062                 return otherSharedTreeView;
1063         }
1064
1065         public void updateTrash(boolean showFiles, Command callback) {
1066                 updateFolder(trash, showFiles, callback, true);
1067         }
1068
1069         public void updateGroupsNode() {
1070                 groupTreeView.updateGroupNode(null);
1071         }
1072
1073         public void addGroup(String groupname) {
1074                 Group newGroup = new Group(groupname);
1075                 account.addGroup(newGroup);
1076                 groupTreeView.updateGroupNode(null);
1077         }
1078
1079         public void removeGroup(Group group) {
1080                 account.removeGroup(group);
1081                 updateGroupsNode();
1082         }
1083
1084         public TreeView getSelectedTree() {
1085                 return selectedTree;
1086         }
1087         
1088         public void setSelectedTree(TreeView selected) {
1089                 selectedTree = selected;
1090         }
1091
1092         public Folder getSelection() {
1093                 if (selectedTree != null)
1094                         return selectedTree.getSelection();
1095                 return null;
1096         }
1097
1098         public void showFolderStatistics(int folderFileCount) {
1099                 numOfFiles.setHTML(String.valueOf(folderFileCount));
1100         }
1101
1102         public GroupTreeView getGroupTreeView() {
1103                 return groupTreeView;
1104         }
1105
1106         public void sessionExpired() {
1107                 new SessionExpiredDialog(this).center();
1108         }
1109
1110         public void updateRootFolder(Command callback) {
1111                 updateFolder(account.getPithos(), false, callback, true);
1112         }
1113
1114         void createMySharedTree() {
1115                 mysharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1116                 mysharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1117                     @Override
1118                     public void onSelectionChange(SelectionChangeEvent event) {
1119                         if (mysharedTreeSelectionModel.getSelectedObject() != null) {
1120                             deselectOthers(mysharedTreeView, mysharedTreeSelectionModel);
1121                             upload.setEnabled(false);
1122                             updateSharedFolder(mysharedTreeSelectionModel.getSelectedObject(), true);
1123                                         showRelevantToolbarButtons();
1124                         }
1125                                 else {
1126                                         if (getSelectedTree().equals(mysharedTreeView))
1127                                                 setSelectedTree(null);
1128                                         if (getSelectedTree() == null)
1129                                                 showRelevantToolbarButtons();
1130                                 }
1131                     }
1132                 });
1133                 selectionModels.add(mysharedTreeSelectionModel);
1134                 mysharedTreeViewModel = new MysharedTreeViewModel(Pithos.this, mysharedTreeSelectionModel);
1135                 mysharedTreeViewModel.initialize(new Command() {
1136                         
1137                         @Override
1138                         public void execute() {
1139                             mysharedTreeView = new MysharedTreeView(mysharedTreeViewModel);
1140                                 trees.insert(mysharedTreeView, 3);
1141                                 treeViews.add(mysharedTreeView);
1142                                 createOtherSharedTree();
1143                         }
1144                 });
1145         }
1146
1147         void createOtherSharedTree() {
1148                 otherSharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1149                 otherSharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1150                     @Override
1151                     public void onSelectionChange(SelectionChangeEvent event) {
1152                         if (otherSharedTreeSelectionModel.getSelectedObject() != null) {
1153                             deselectOthers(otherSharedTreeView, otherSharedTreeSelectionModel);
1154                             otherSharedTreeView.addStyleName("cellTreeWidget-selectedTree");
1155                             applyPermissions(otherSharedTreeSelectionModel.getSelectedObject());
1156                             updateOtherSharedFolder(otherSharedTreeSelectionModel.getSelectedObject(), true);
1157                                         showRelevantToolbarButtons();
1158                         }
1159                                 else {
1160                                         if (getSelectedTree().equals(otherSharedTreeView))
1161                                                 setSelectedTree(null);
1162                                         if (getSelectedTree() == null)
1163                                                 showRelevantToolbarButtons();
1164                                 }
1165                     }
1166                 });
1167                 selectionModels.add(otherSharedTreeSelectionModel);
1168                 otherSharedTreeViewModel = new OtherSharedTreeViewModel(Pithos.this, otherSharedTreeSelectionModel);
1169                 otherSharedTreeViewModel.initialize(new Command() {
1170                         
1171                         @Override
1172                         public void execute() {
1173                             otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel);
1174                                 trees.insert(otherSharedTreeView, 3);
1175                                 treeViews.add(otherSharedTreeView);
1176                         }
1177                 });
1178         }
1179
1180         public native void log1(String message)/*-{
1181                 $wnd.console.log(message);
1182         }-*/;
1183
1184         public String getErrorData() {
1185                 if (error != null)
1186                         return error.toString();
1187                 return "";
1188         }
1189         
1190         public void setError(Throwable t) {
1191                 error = t;
1192         }
1193         
1194         public void showRelevantToolbarButtons() {
1195                 toolbar.showRelevantButtons();
1196         }
1197
1198         public FileUploadDialog getFileUploadDialog() {
1199                 if (fileUploadDialog == null)
1200                         fileUploadDialog = new FileUploadDialog(this);
1201                 return fileUploadDialog;
1202         }
1203
1204         public void hideUploadIndicator() {
1205                 upload.removeStyleName("pithos-uploadButton-loading");
1206                 upload.setTitle("");
1207         }
1208         
1209         public void showUploadIndicator() {
1210                 upload.addStyleName("pithos-uploadButton-loading");
1211                 upload.setTitle("Upload in progress. Click for details.");
1212         }
1213
1214         public void scheduleFolderHeadCommand(final Folder folder, final Command callback) {
1215                 if (folder == null) {
1216                         if (callback != null)
1217                                 callback.execute();
1218                 }
1219                 else {
1220                         HeadRequest<Folder> headFolder = new HeadRequest<Folder>(Folder.class, getApiPath(), folder.getOwner(), folder.getUri(), folder) {
1221         
1222                                 @Override
1223                                 public void onSuccess(Folder _result) {
1224                                         if (callback != null)
1225                                                 callback.execute();
1226                                 }
1227         
1228                                 @Override
1229                                 public void onError(Throwable t) {
1230                                 if (t instanceof RestException) {
1231                                         if (((RestException) t).getHttpStatusCode() == Response.SC_NOT_FOUND) {
1232                                 final String path = folder.getUri();
1233                                 PutRequest newFolder = new PutRequest(getApiPath(), folder.getOwner(), path) {
1234                                     @Override
1235                                     public void onSuccess(Resource _result) {
1236                                         scheduleFolderHeadCommand(folder, callback);
1237                                     }
1238         
1239                                     @Override
1240                                     public void onError(Throwable _t) {
1241                                         GWT.log("", _t);
1242                                                                 setError(_t);
1243                                         if(_t instanceof RestException){
1244                                             displayError("Unable to create folder: " + ((RestException) _t).getHttpStatusText());
1245                                         }
1246                                         else
1247                                             displayError("System error creating folder: " + _t.getMessage());
1248                                     }
1249         
1250                                                 @Override
1251                                                 protected void onUnauthorized(Response response) {
1252                                                         sessionExpired();
1253                                                 }
1254                                 };
1255                                 newFolder.setHeader("X-Auth-Token", getToken());
1256                                 newFolder.setHeader("Content-Type", "application/folder");
1257                                 newFolder.setHeader("Accept", "*/*");
1258                                 newFolder.setHeader("Content-Length", "0");
1259                                 Scheduler.get().scheduleDeferred(newFolder);
1260                                         }
1261                                         else
1262                                                 displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
1263                                 }
1264                                 else
1265                                     displayError("System error heading folder: " + t.getMessage());
1266         
1267                                 GWT.log("Error heading folder", t);
1268                                         setError(t);
1269                                 }
1270         
1271                                 @Override
1272                                 protected void onUnauthorized(Response response) {
1273                                         sessionExpired();
1274                                 }
1275                         };
1276                         headFolder.setHeader("X-Auth-Token", getToken());
1277                         Scheduler.get().scheduleDeferred(headFolder);
1278                 }
1279         }
1280
1281         public void scheduleFileHeadCommand(File f, final Command callback) {
1282                 HeadRequest<File> headFile = new HeadRequest<File>(File.class, getApiPath(), f.getOwner(), f.getUri(), f) {
1283
1284                         @Override
1285                         public void onSuccess(File _result) {
1286                                 if (callback != null)
1287                                         callback.execute();
1288                         }
1289
1290                         @Override
1291                         public void onError(Throwable t) {
1292                         GWT.log("Error heading file", t);
1293                                 setError(t);
1294                         if (t instanceof RestException)
1295                             displayError("Error heading file: " + ((RestException) t).getHttpStatusText());
1296                         else
1297                             displayError("System error heading file: " + t.getMessage());
1298                         }
1299
1300                         @Override
1301                         protected void onUnauthorized(Response response) {
1302                                 sessionExpired();
1303                         }
1304                 };
1305                 headFile.setHeader("X-Auth-Token", getToken());
1306                 Scheduler.get().scheduleDeferred(headFile);
1307         }
1308 }