Get Storage Logo from JS properties
[pithos-web-client] / src / gr / grnet / pithos / web / client / Pithos.java
1 /*
2  * Copyright 2011-2013 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 com.google.gwt.core.client.EntryPoint;
38 import com.google.gwt.core.client.GWT;
39 import com.google.gwt.core.client.JsArrayString;
40 import com.google.gwt.core.client.Scheduler;
41 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
42 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
43 import com.google.gwt.event.dom.client.ClickEvent;
44 import com.google.gwt.event.dom.client.ClickHandler;
45 import com.google.gwt.event.logical.shared.ResizeEvent;
46 import com.google.gwt.event.logical.shared.ResizeHandler;
47 import com.google.gwt.http.client.Response;
48 import com.google.gwt.http.client.URL;
49 import com.google.gwt.i18n.client.DateTimeFormat;
50 import com.google.gwt.i18n.client.Dictionary;
51 import com.google.gwt.i18n.client.TimeZone;
52 import com.google.gwt.resources.client.ClientBundle;
53 import com.google.gwt.resources.client.CssResource;
54 import com.google.gwt.resources.client.ImageResource;
55 import com.google.gwt.resources.client.ImageResource.ImageOptions;
56 import com.google.gwt.user.client.*;
57 import com.google.gwt.user.client.ui.*;
58 import com.google.gwt.view.client.SelectionChangeEvent;
59 import com.google.gwt.view.client.SelectionChangeEvent.Handler;
60 import com.google.gwt.view.client.SingleSelectionModel;
61 import gr.grnet.pithos.web.client.catalog.UpdateUserCatalogs;
62 import gr.grnet.pithos.web.client.catalog.UserCatalogs;
63 import gr.grnet.pithos.web.client.commands.UploadFileCommand;
64 import gr.grnet.pithos.web.client.foldertree.*;
65 import gr.grnet.pithos.web.client.grouptree.Group;
66 import gr.grnet.pithos.web.client.grouptree.GroupTreeView;
67 import gr.grnet.pithos.web.client.grouptree.GroupTreeViewModel;
68 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeView;
69 import gr.grnet.pithos.web.client.mysharedtree.MysharedTreeViewModel;
70 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeView;
71 import gr.grnet.pithos.web.client.othersharedtree.OtherSharedTreeViewModel;
72 import gr.grnet.pithos.web.client.rest.*;
73 import org.apache.http.HttpStatus;
74
75 import java.util.*;
76
77 /**
78  * Entry point classes define <code>onModuleLoad()</code>.
79  */
80 public class Pithos implements EntryPoint, ResizeHandler {
81     private static final boolean IsLOGEnabled = false;
82     public static final boolean IsDetailedHTTPLOGEnabled = true;
83     public static final boolean IsFullResponseBodyLOGEnabled = true;
84
85     public static final Set<String> HTTPHeadersToIgnoreInLOG = new HashSet<String>();
86     static {
87         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_CONNECTION);
88         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_DATE);
89         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_KEEP_ALIVE);
90         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_SERVER);
91         HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_VARY);
92         HTTPHeadersToIgnoreInLOG.add(Const.IF_MODIFIED_SINCE);
93     }
94
95     public static final Configuration config = GWT.create(Configuration.class);
96
97     public interface Style extends CssResource {
98         String commandAnchor();
99
100         String statistics();
101
102         @ClassName("gwt-HTML")
103         String html();
104
105         String uploadAlert();
106
107         String uploadAlertLink();
108
109         String uploadAlertProgress();
110
111         String uploadAlertPercent();
112
113         String uploadAlertClose();
114     }
115
116     public interface Resources extends ClientBundle {
117         @Source("Pithos.css")
118         Style pithosCss();
119
120         @Source("gr/grnet/pithos/resources/close-popup.png")
121         ImageResource closePopup();
122     }
123
124     public static Resources resources = GWT.create(Resources.class);
125
126     /**
127      * Instantiate an application-level image bundle. This object will provide
128      * programmatic access to all the images needed by widgets.
129      */
130     static Images images = (Images) GWT.create(Images.class);
131
132     public String getUserID() {
133         return userID;
134     }
135
136     public UserCatalogs getUserCatalogs() {
137         return userCatalogs;
138     }
139
140     public String getCurrentUserDisplayNameOrID() {
141         final String displayName = userCatalogs.getDisplayName(getUserID());
142         return displayName == null ? getUserID() : displayName;
143     }
144
145     public boolean hasDisplayNameForUserID(String userID) {
146         return userCatalogs.getDisplayName(userID) != null;
147     }
148
149     public boolean hasIDForUserDisplayName(String userDisplayName) {
150         return userCatalogs.getID(userDisplayName) != null;
151     }
152
153     public String getDisplayNameForUserID(String userID) {
154         return userCatalogs.getDisplayName(userID);
155     }
156
157     public String getIDForUserDisplayName(String userDisplayName) {
158         return userCatalogs.getID(userDisplayName);
159     }
160
161     public List<String> getDisplayNamesForUserIDs(List<String> userIDs) {
162         if(userIDs == null) {
163             userIDs = new ArrayList<String>();
164         }
165         final List<String> userDisplayNames = new ArrayList<String>();
166         for(String userID : userIDs) {
167             final String displayName = getDisplayNameForUserID(userID);
168             userDisplayNames.add(displayName);
169         }
170
171         return userDisplayNames;
172     }
173
174     public List<String> filterUserIDsWithUnknownDisplayName(Collection<String> userIDs) {
175         if(userIDs == null) {
176             userIDs = new ArrayList<String>();
177         }
178         final List<String> filtered = new ArrayList<String>();
179         for(String userID : userIDs) {
180             if(!this.userCatalogs.hasID(userID)) {
181                 filtered.add(userID);
182             }
183         }
184         return filtered;
185     }
186
187     public void setAccount(AccountResource acct) {
188         account = acct;
189     }
190
191     public AccountResource getAccount() {
192         return account;
193     }
194
195     public void updateFolder(Folder f, boolean showfiles, Command callback, final boolean openParent) {
196         folderTreeView.updateFolder(f, showfiles, callback, openParent);
197     }
198
199     public void updateGroupNode(Group group) {
200         groupTreeView.updateGroupNode(group);
201     }
202
203     public void updateMySharedRoot() {
204         mysharedTreeView.updateRoot();
205     }
206
207     public void updateSharedFolder(Folder f, boolean showfiles, Command callback) {
208         mysharedTreeView.updateFolder(f, showfiles, callback);
209     }
210
211     public void updateSharedFolder(Folder f, boolean showfiles) {
212         updateSharedFolder(f, showfiles, null);
213     }
214
215     public void updateOtherSharedFolder(Folder f, boolean showfiles, Command callback) {
216         otherSharedTreeView.updateFolder(f, showfiles, callback);
217     }
218
219     public MysharedTreeView getMySharedTreeView() {
220         return mysharedTreeView;
221     }
222
223     /**
224      * An aggregate image bundle that pulls together all the images for this
225      * application into a single bundle.
226      */
227     public interface Images extends TopPanel.Images, FileList.Images, ToolsMenu.Images {
228
229         @Source("gr/grnet/pithos/resources/document.png")
230         ImageResource folders();
231
232         @Source("gr/grnet/pithos/resources/advancedsettings.png")
233         @ImageOptions(width = 32, height = 32)
234         ImageResource tools();
235     }
236
237     private Throwable error;
238
239     /**
240      * The Application Clipboard implementation;
241      */
242     private Clipboard clipboard = new Clipboard();
243
244     /**
245      * The top panel that contains the menu bar.
246      */
247     private TopPanel topPanel;
248
249     /**
250      * The panel that contains the various system messages.
251      */
252     private MessagePanel messagePanel = new MessagePanel(this, Pithos.images);
253
254     /**
255      * The bottom panel that contains the status bar.
256      */
257     StatusPanel statusPanel = null;
258
259     /**
260      * The file list widget.
261      */
262     private FileList fileList;
263
264     /**
265      * The tab panel that occupies the right side of the screen.
266      */
267     private VerticalPanel inner = new VerticalPanel();
268
269
270     /**
271      * The split panel that will contain the left and right panels.
272      */
273     private HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
274
275     /**
276      * The currently selected item in the application, for use by the Edit menu
277      * commands. Potential types are Folder, File, User and Group.
278      */
279     private Object currentSelection;
280
281     public HashMap<String, String> userFullNameMap = new HashMap<String, String>();
282
283     /**
284      * The ID that uniquely identifies the user in Pithos+.
285      * Currently this is a UUID. It used to be the user's email.
286      */
287     private String userID = null;
288
289     /**
290      * Hold mappings from user UUIDs to emails and vice-versa.
291      */
292     private UserCatalogs userCatalogs = new UserCatalogs();
293
294     /**
295      * The authentication token of the current user.
296      */
297     private String userToken;
298
299     VerticalPanel trees;
300
301     SingleSelectionModel<Folder> folderTreeSelectionModel;
302     FolderTreeViewModel folderTreeViewModel;
303     FolderTreeView folderTreeView;
304
305     SingleSelectionModel<Folder> mysharedTreeSelectionModel;
306     MysharedTreeViewModel mysharedTreeViewModel;
307     MysharedTreeView mysharedTreeView = null;
308
309     protected SingleSelectionModel<Folder> otherSharedTreeSelectionModel;
310     OtherSharedTreeViewModel otherSharedTreeViewModel;
311     OtherSharedTreeView otherSharedTreeView = null;
312
313     GroupTreeViewModel groupTreeViewModel;
314     GroupTreeView groupTreeView;
315
316     TreeView selectedTree;
317     protected AccountResource account;
318
319     Folder trash;
320
321     List<Composite> treeViews = new ArrayList<Composite>();
322
323     @SuppressWarnings("rawtypes")
324     List<SingleSelectionModel> selectionModels = new ArrayList<SingleSelectionModel>();
325
326     public Button upload;
327
328     private HTML numOfFiles;
329
330     private Toolbar toolbar;
331
332     private FileUploadDialog fileUploadDialog = new FileUploadDialog(this);
333
334     UploadAlert uploadAlert;
335
336     Date lastModified;
337
338     @Override
339     public void onModuleLoad() {
340         if(parseUserCredentials()) {
341             initialize();
342         }
343     }
344
345     static native void __ConsoleLog(String message) /*-{
346       try { console.log(message); } catch (e) {}
347     }-*/;
348
349     public static void LOGError(Throwable error, StringBuilder sb) {
350         if(!isLOGEnabled()) { return; }
351
352         sb.append("\nException: [" + error.toString().replace("\n", "\n  ") + "]");
353         Throwable cause = error.getCause();
354         if(cause != null) {
355             sb.append("\nCauses:\n");
356             while(cause != null) {
357                 sb.append("  ");
358                 sb.append("[" + cause.toString().replace("\n", "\n  ")  + "]");
359                 sb.append("\n");
360                 cause = cause.getCause();
361             }
362         }
363         else {
364             sb.append("\n");
365         }
366
367         StackTraceElement[] stackTrace = error.getStackTrace();
368         sb.append("Stack trace (" + stackTrace.length + " elements):\n");
369         for(int i = 0; i < stackTrace.length; i++) {
370             StackTraceElement errorElem = stackTrace[i];
371             sb.append("  [" + i + "] ");
372             sb.append(errorElem.toString());
373             sb.append("\n");
374         }
375     }
376
377     public static void LOGError(Throwable error) {
378         if(!isLOGEnabled()) { return; }
379
380         final StringBuilder sb = new StringBuilder();
381         LOGError(error, sb);
382         if(sb.length() > 0) {
383             __ConsoleLog(sb.toString());
384         }
385     }
386
387     public static boolean isLOGEnabled() {
388         return IsLOGEnabled;
389     }
390
391     public static void LOG(Object ...args) {
392         if(!isLOGEnabled()) { return; }
393
394         final StringBuilder sb = new StringBuilder();
395         for(Object arg : args) {
396             if(arg instanceof Throwable) {
397                 LOGError((Throwable) arg, sb);
398             }
399             else {
400                 sb.append(arg);
401             }
402         }
403
404         if(sb.length() > 0) {
405             __ConsoleLog(sb.toString());
406         }
407     }
408
409     private void initialize() {
410         userCatalogs.updateWithIDAndName("*", "All Pithos users");
411
412         lastModified = new Date(); //Initialize if-modified-since value with now.
413         resources.pithosCss().ensureInjected();
414         boolean bareContent = Window.Location.getParameter("noframe") != null;
415         String contentWidth = bareContent ? Const.PERCENT_100 : Const.PERCENT_75;
416
417         VerticalPanel outer = new VerticalPanel();
418         outer.setWidth(Const.PERCENT_100);
419         if(!bareContent) {
420             outer.addStyleName("pithos-outer");
421         }
422
423         if(!bareContent) {
424             topPanel = new TopPanel(this, Pithos.images);
425             topPanel.setWidth(Const.PERCENT_100);
426             outer.add(topPanel);
427             outer.setCellHorizontalAlignment(topPanel, HasHorizontalAlignment.ALIGN_CENTER);
428         }
429
430         messagePanel.setVisible(false);
431         outer.add(messagePanel);
432         outer.setCellHorizontalAlignment(messagePanel, HasHorizontalAlignment.ALIGN_CENTER);
433         outer.setCellVerticalAlignment(messagePanel, HasVerticalAlignment.ALIGN_MIDDLE);
434
435         HorizontalPanel header = new HorizontalPanel();
436         header.addStyleName("pithos-header");
437         header.setWidth(contentWidth);
438         if(bareContent) {
439             header.addStyleName("pithos-header-noframe");
440         }
441         upload = new Button("Upload", new ClickHandler() {
442             @Override
443             public void onClick(ClickEvent event) {
444                 if(getSelection() != null) {
445                     new UploadFileCommand(Pithos.this, null, getSelection()).execute();
446                 }
447             }
448         });
449         upload.addStyleName("pithos-uploadButton");
450         header.add(upload);
451         header.setCellHorizontalAlignment(upload, HasHorizontalAlignment.ALIGN_LEFT);
452         header.setCellVerticalAlignment(upload, HasVerticalAlignment.ALIGN_MIDDLE);
453
454         toolbar = new Toolbar(this);
455         header.add(toolbar);
456         header.setCellHorizontalAlignment(toolbar, HasHorizontalAlignment.ALIGN_CENTER);
457         header.setCellVerticalAlignment(toolbar, HasVerticalAlignment.ALIGN_MIDDLE);
458
459         HorizontalPanel folderStatistics = new HorizontalPanel();
460         folderStatistics.addStyleName("pithos-folderStatistics");
461         numOfFiles = new HTML();
462         folderStatistics.add(numOfFiles);
463         folderStatistics.setCellVerticalAlignment(numOfFiles, HasVerticalAlignment.ALIGN_MIDDLE);
464         HTML numOfFilesLabel = new HTML("&nbsp;Files");
465         folderStatistics.add(numOfFilesLabel);
466         folderStatistics.setCellVerticalAlignment(numOfFilesLabel, HasVerticalAlignment.ALIGN_MIDDLE);
467         header.add(folderStatistics);
468         header.setCellHorizontalAlignment(folderStatistics, HasHorizontalAlignment.ALIGN_RIGHT);
469         header.setCellVerticalAlignment(folderStatistics, HasVerticalAlignment.ALIGN_MIDDLE);
470         header.setCellWidth(folderStatistics, "40px");
471         outer.add(header);
472         outer.setCellHorizontalAlignment(header, HasHorizontalAlignment.ALIGN_CENTER);
473         // Inner contains the various lists
474         inner.sinkEvents(Event.ONCONTEXTMENU);
475         inner.setWidth(Const.PERCENT_100);
476
477         folderTreeSelectionModel = new SingleSelectionModel<Folder>();
478         folderTreeSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
479             @Override
480             public void onSelectionChange(SelectionChangeEvent event) {
481                 if(folderTreeSelectionModel.getSelectedObject() != null) {
482                     deselectOthers(folderTreeView, folderTreeSelectionModel);
483                     applyPermissions(folderTreeSelectionModel.getSelectedObject());
484                     Folder f = folderTreeSelectionModel.getSelectedObject();
485                     updateFolder(f, true, new Command() {
486
487                         @Override
488                         public void execute() {
489                             updateStatistics();
490                         }
491                     }, true);
492                     showRelevantToolbarButtons();
493                 }
494                 else {
495                     if(getSelectedTree().equals(folderTreeView)) {
496                         setSelectedTree(null);
497                     }
498                     if(getSelectedTree() == null) {
499                         showRelevantToolbarButtons();
500                     }
501                 }
502             }
503         });
504         selectionModels.add(folderTreeSelectionModel);
505
506         folderTreeViewModel = new FolderTreeViewModel(this, folderTreeSelectionModel);
507         folderTreeView = new FolderTreeView(folderTreeViewModel);
508         treeViews.add(folderTreeView);
509
510         fileList = new FileList(this, images);
511         inner.add(fileList);
512
513         trees = new VerticalPanel();
514         trees.setWidth(Const.PERCENT_100);
515
516         // Add the left and right panels to the split panel.
517         splitPanel.setLeftWidget(trees);
518         FlowPanel right = new FlowPanel();
519         right.getElement().setId("rightPanel");
520         right.add(inner);
521         splitPanel.setRightWidget(right);
522         splitPanel.setSplitPosition("219px");
523         splitPanel.setSize(Const.PERCENT_100, Const.PERCENT_100);
524         splitPanel.addStyleName("pithos-splitPanel");
525         splitPanel.setWidth(contentWidth);
526         outer.add(splitPanel);
527         outer.setCellHorizontalAlignment(splitPanel, HasHorizontalAlignment.ALIGN_CENTER);
528
529         if(!bareContent) {
530             statusPanel = new StatusPanel();
531             statusPanel.setWidth(Const.PERCENT_100);
532             outer.add(statusPanel);
533             outer.setCellHorizontalAlignment(statusPanel, HasHorizontalAlignment.ALIGN_CENTER);
534         }
535         else {
536             splitPanel.addStyleName("pithos-splitPanel-noframe");
537         }
538
539         // Hook the window resize event, so that we can adjust the UI.
540         Window.addResizeHandler(this);
541         // Clear out the window's built-in margin, because we want to take
542         // advantage of the entire client area.
543         Window.setMargin("0px");
544         // Finally, add the outer panel to the RootPanel, so that it will be
545         // displayed.
546         RootPanel.get().add(outer);
547         // Call the window resized handler to get the initial sizes setup. Doing
548         // this in a deferred command causes it to occur after all widgets'
549         // sizes have been computed by the browser.
550         Scheduler.get().scheduleIncremental(new RepeatingCommand() {
551
552             @Override
553             public boolean execute() {
554                 if(!isCloudbarReady()) {
555                     return true;
556                 }
557                 onWindowResized(Window.getClientHeight());
558                 return false;
559             }
560         });
561
562         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
563             @Override
564             public void execute() {
565                 LOG("Pithos::initialize() Calling Pithos::fetchAccount()");
566                 fetchAccount(new Command() {
567
568                     @Override
569                     public void execute() {
570                         if(!account.hasHomeContainer()) {
571                             createHomeContainer(account, this);
572                         }
573                         else if(!account.hasTrashContainer()) {
574                             createTrashContainer(this);
575                         }
576                         else {
577                             for(Folder f : account.getContainers()) {
578                                 if(f.getName().equals(Const.TRASH_CONTAINER)) {
579                                     trash = f;
580                                     break;
581                                 }
582                             }
583                             trees.add(folderTreeView);
584                             folderTreeViewModel.initialize(account, new Command() {
585
586                                 @Override
587                                 public void execute() {
588                                     createMySharedTree();
589                                 }
590                             });
591
592                             HorizontalPanel separator = new HorizontalPanel();
593                             separator.addStyleName("pithos-statisticsSeparator");
594                             separator.add(new HTML(""));
595                             trees.add(separator);
596
597                             groupTreeViewModel = new GroupTreeViewModel(Pithos.this);
598                             groupTreeView = new GroupTreeView(groupTreeViewModel);
599                             treeViews.add(groupTreeView);
600                             trees.add(groupTreeView);
601                             folderTreeView.showStatistics(account);
602                         }
603                     }
604                 });
605             }
606         });
607     }
608
609     public void scheduleResfresh() {
610         Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
611
612             @Override
613             public boolean execute() {
614                 final Folder f = getSelection();
615                 if(f == null) {
616                     return true;
617                 }
618
619                 HeadRequest<Folder> head = new HeadRequest<Folder>(Folder.class, getApiPath(), f.getOwnerID(), "/" + f.getContainer()) {
620
621                     @Override
622                     public void onSuccess(Folder _result) {
623                         lastModified = new Date();
624                         if(getSelectedTree().equals(folderTreeView)) {
625                             updateFolder(f, true, new Command() {
626
627                                 @Override
628                                 public void execute() {
629                                     scheduleResfresh();
630                                 }
631
632                             }, false);
633                         }
634                         else if(getSelectedTree().equals(mysharedTreeView)) {
635                             updateSharedFolder(f, true, new Command() {
636
637                                 @Override
638                                 public void execute() {
639                                     scheduleResfresh();
640                                 }
641                             });
642                         }
643                         else {
644                             scheduleResfresh();
645                         }
646                     }
647
648                     @Override
649                     public void onError(Throwable t) {
650                         if(t instanceof RestException && ((RestException) t).getHttpStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
651                             scheduleResfresh();
652                         }
653                         else if(retries >= MAX_RETRIES) {
654                             LOG("Error heading folder. ", t);
655                             setError(t);
656                             if(t instanceof RestException) {
657                                 displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
658                             }
659                             else {
660                                 displayError("System error heading folder: " + t.getMessage());
661                             }
662                         }
663                         else {//retry
664                             LOG("Retry ", retries);
665                             Scheduler.get().scheduleDeferred(this);
666                         }
667                     }
668
669                     @Override
670                     protected void onUnauthorized(Response response) {
671                         if(retries >= MAX_RETRIES) {
672                             sessionExpired();
673                         }
674                         else //retry
675                         {
676                             Scheduler.get().scheduleDeferred(this);
677                         }
678                     }
679                 };
680                 head.setHeader(Const.X_AUTH_TOKEN, getUserToken());
681                 head.setHeader(Const.IF_MODIFIED_SINCE, DateTimeFormat.getFormat(Const.DATE_FORMAT_1).format(lastModified, TimeZone.createTimeZone(0)) + " GMT");
682                 Scheduler.get().scheduleDeferred(head);
683
684                 return false;
685             }
686         }, 3000);
687     }
688
689     public void applyPermissions(Folder f) {
690         if(f != null) {
691             if(f.isInTrash()) {
692                 upload.setEnabled(false);
693                 disableUploadArea();
694             }
695             else {
696                 Boolean[] perms = f.getPermissions().get(userID);
697                 if(f.getOwnerID().equals(userID) || (perms != null && perms[1] != null && perms[1])) {
698                     upload.setEnabled(true);
699                     enableUploadArea();
700                 }
701                 else {
702                     upload.setEnabled(false);
703                     disableUploadArea();
704                 }
705             }
706         }
707         else {
708             upload.setEnabled(false);
709             disableUploadArea();
710         }
711     }
712
713     @SuppressWarnings({"rawtypes", "unchecked"})
714     public void deselectOthers(TreeView _selectedTree, SingleSelectionModel model) {
715         selectedTree = _selectedTree;
716
717         for(SingleSelectionModel s : selectionModels) {
718             if(!s.equals(model) && s.getSelectedObject() != null) {
719                 s.setSelected(s.getSelectedObject(), false);
720             }
721         }
722     }
723
724     public void showFiles(final Folder f) {
725         Set<File> files = f.getFiles();
726         showFiles(files);
727     }
728
729     public void showFiles(Set<File> files) {
730         fileList.setFiles(new ArrayList<File>(files));
731     }
732
733     /**
734      * Parse and store the user credentials to the appropriate fields.
735      */
736     private boolean parseUserCredentials() {
737         Configuration conf = (Configuration) GWT.create(Configuration.class);
738         Dictionary otherProperties = Dictionary.getDictionary(Const.OTHER_PROPERTIES);
739         String cookie = otherProperties.get(Const.AUTH_COOKIE);
740         String auth = Cookies.getCookie(cookie);
741         if(auth == null) {
742             authenticateUser();
743             return false;
744         }
745         if(auth.startsWith("\"")) {
746             auth = auth.substring(1);
747         }
748         if(auth.endsWith("\"")) {
749             auth = auth.substring(0, auth.length() - 1);
750         }
751         String[] authSplit = auth.split("\\" + conf.cookieSeparator(), 2);
752         if(authSplit.length != 2) {
753             authenticateUser();
754             return false;
755         }
756         userID = authSplit[0];
757         userToken = authSplit[1];
758
759         String gotoUrl = Window.Location.getParameter("goto");
760         if(gotoUrl != null && gotoUrl.length() > 0) {
761             Window.Location.assign(gotoUrl);
762             return false;
763         }
764         return true;
765     }
766
767     /**
768      * Redirect the user to the login page for authentication.
769      */
770     protected void authenticateUser() {
771         Dictionary otherProperties = Dictionary.getDictionary(Const.OTHER_PROPERTIES);
772         Window.Location.assign(otherProperties.get(Const.LOGIN_URL) + Window.Location.getHref());
773     }
774
775     public void fetchAccount(final Command callback) {
776         String path = "?format=json";
777
778         GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getApiPath(), userID, path) {
779             @Override
780             public void onSuccess(AccountResource accountResource) {
781                 account = accountResource;
782                 if(callback != null) {
783                     callback.execute();
784                 }
785
786                 final List<String> memberIDs = new ArrayList<String>();
787                 final List<Group> groups = account.getGroups();
788                 for(Group group : groups) {
789                     memberIDs.addAll(group.getMemberIDs());
790                 }
791                 memberIDs.add(Pithos.this.getUserID());
792
793                 final List<String> theUnknown = Pithos.this.filterUserIDsWithUnknownDisplayName(memberIDs);
794                 // Initialize the user catalog
795                 new UpdateUserCatalogs(Pithos.this, theUnknown).scheduleDeferred();
796                 LOG("Called new UpdateUserCatalogs(Pithos.this, theUnknown).scheduleDeferred();");
797             }
798
799             @Override
800             public void onError(Throwable t) {
801                 LOG("Error getting account", t);
802                 setError(t);
803                 if(t instanceof RestException) {
804                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
805                 }
806                 else {
807                     displayError("System error fetching user data: " + t.getMessage());
808                 }
809             }
810
811             @Override
812             protected void onUnauthorized(Response response) {
813                 sessionExpired();
814             }
815         };
816         getAccount.setHeader(Const.X_AUTH_TOKEN, userToken);
817         Scheduler.get().scheduleDeferred(getAccount);
818     }
819
820     public void updateStatistics() {
821         HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getApiPath(), userID, "", account) {
822
823             @Override
824             public void onSuccess(AccountResource _result) {
825                 folderTreeView.showStatistics(account);
826             }
827
828             @Override
829             public void onError(Throwable t) {
830                 LOG("Error getting account", t);
831                 setError(t);
832                 if(t instanceof RestException) {
833                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
834                 }
835                 else {
836                     displayError("System error fetching user data: " + t.getMessage());
837                 }
838             }
839
840             @Override
841             protected void onUnauthorized(Response response) {
842                 sessionExpired();
843             }
844         };
845         headAccount.setHeader(Const.X_AUTH_TOKEN, userToken);
846         Scheduler.get().scheduleDeferred(headAccount);
847     }
848
849     protected void createHomeContainer(final AccountResource _account, final Command callback) {
850         String path = "/" + Const.HOME_CONTAINER;
851         PutRequest createPithos = new PutRequest(getApiPath(), getUserID(), path) {
852             @Override
853             public void onSuccess(Resource result) {
854                 if(!_account.hasTrashContainer()) {
855                     createTrashContainer(callback);
856                 }
857                 else {
858                     fetchAccount(callback);
859                 }
860             }
861
862             @Override
863             public void onError(Throwable t) {
864                 LOG("Error creating pithos", t);
865                 setError(t);
866                 if(t instanceof RestException) {
867                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
868                 }
869                 else {
870                     displayError("System error Error creating pithos: " + t.getMessage());
871                 }
872             }
873
874             @Override
875             protected void onUnauthorized(Response response) {
876                 sessionExpired();
877             }
878         };
879         createPithos.setHeader(Const.X_AUTH_TOKEN, getUserToken());
880         Scheduler.get().scheduleDeferred(createPithos);
881     }
882
883     protected void createTrashContainer(final Command callback) {
884         String path = "/" + Const.TRASH_CONTAINER;
885         PutRequest createPithos = new PutRequest(getApiPath(), getUserID(), path) {
886             @Override
887             public void onSuccess(Resource result) {
888                 fetchAccount(callback);
889             }
890
891             @Override
892             public void onError(Throwable t) {
893                 LOG("Error creating pithos", t);
894                 setError(t);
895                 if(t instanceof RestException) {
896                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
897                 }
898                 else {
899                     displayError("System error Error creating pithos: " + t.getMessage());
900                 }
901             }
902
903             @Override
904             protected void onUnauthorized(Response response) {
905                 sessionExpired();
906             }
907         };
908         createPithos.setHeader(Const.X_AUTH_TOKEN, getUserToken());
909         Scheduler.get().scheduleDeferred(createPithos);
910     }
911
912     /**
913      * Creates an HTML fragment that places an image & caption together, for use
914      * in a group header.
915      *
916      * @param imageProto an image prototype for an image
917      * @param caption    the group caption
918      * @return the header HTML fragment
919      */
920     private String createHeaderHTML(AbstractImagePrototype imageProto, String caption) {
921         String captionHTML = "<table class='caption' cellpadding='0' "
922             + "cellspacing='0'>" + "<tr><td class='lcaption'>" + imageProto.getHTML()
923             + "</td><td id =" + caption + " class='rcaption'><b style='white-space:nowrap'>&nbsp;"
924             + caption + "</b></td></tr></table>";
925         return captionHTML;
926     }
927
928     protected void onWindowResized(int height) {
929         // Adjust the split panel to take up the available room in the window.
930         int newHeight = height - splitPanel.getAbsoluteTop() - 153;
931         if(newHeight < 1) {
932             newHeight = 1;
933         }
934         splitPanel.setHeight("" + newHeight);
935         inner.setHeight("" + newHeight);
936     }
937
938     native boolean isCloudbarReady()/*-{
939       if ($wnd.$("div.cloudbar") && $wnd.$("div.cloudbar").height() > 0)
940         return true;
941       return false;
942     }-*/;
943
944     @Override
945     public void onResize(ResizeEvent event) {
946         int height = event.getHeight();
947         onWindowResized(height);
948     }
949
950     /**
951      * Display an error message.
952      *
953      * @param msg the message to display
954      */
955     public void displayError(String msg) {
956         messagePanel.displayError(msg);
957         onWindowResized(Window.getClientHeight());
958     }
959
960     /**
961      * Display a warning message.
962      *
963      * @param msg the message to display
964      */
965     public void displayWarning(String msg) {
966         messagePanel.displayWarning(msg);
967         onWindowResized(Window.getClientHeight());
968     }
969
970     /**
971      * Display an informational message.
972      *
973      * @param msg the message to display
974      */
975     public void displayInformation(String msg) {
976         messagePanel.displayInformation(msg);
977         onWindowResized(Window.getClientHeight());
978     }
979
980     /**
981      * Retrieve the fileList.
982      *
983      * @return the fileList
984      */
985     public FileList getFileList() {
986         return fileList;
987     }
988
989     /**
990      * Retrieve the topPanel.
991      *
992      * @return the topPanel
993      */
994     TopPanel getTopPanel() {
995         return topPanel;
996     }
997
998     /**
999      * Retrieve the clipboard.
1000      *
1001      * @return the clipboard
1002      */
1003     public Clipboard getClipboard() {
1004         return clipboard;
1005     }
1006
1007     public StatusPanel getStatusPanel() {
1008         return statusPanel;
1009     }
1010
1011     public String getUserToken() {
1012         return userToken;
1013     }
1014
1015     public static native void preventIESelection() /*-{
1016       $doc.body.onselectstart = function () {
1017         return false;
1018       };
1019     }-*/;
1020
1021     public static native void enableIESelection() /*-{
1022       if ($doc.body.onselectstart != null)
1023         $doc.body.onselectstart = null;
1024     }-*/;
1025
1026     /**
1027      * @return the absolute path of the API root URL
1028      */
1029     public String getApiPath() {
1030         Configuration conf = (Configuration) GWT.create(Configuration.class);
1031         return conf.apiPath();
1032     }
1033
1034     /**
1035      * History support for folder navigation
1036      * adds a new browser history entry
1037      *
1038      * @param key
1039      */
1040     public void updateHistory(String key) {
1041 //              Replace any whitespace of the initial string to "+"
1042 //              String result = key.replaceAll("\\s","+");
1043 //              Add a new browser history entry.
1044 //              History.newItem(result);
1045         History.newItem(key);
1046     }
1047
1048     public void deleteFolder(final Folder folder, final Command callback) {
1049         final PleaseWaitPopup pwp = new PleaseWaitPopup();
1050         pwp.center();
1051         String path = "/" + folder.getContainer() + "/" + folder.getPrefix() + "?delimiter=/" + "&t=" + System.currentTimeMillis();
1052         DeleteRequest deleteFolder = new DeleteRequest(getApiPath(), folder.getOwnerID(), path) {
1053
1054             @Override
1055             protected void onUnauthorized(Response response) {
1056                 pwp.hide();
1057                 sessionExpired();
1058             }
1059
1060             @Override
1061             public void onSuccess(Resource result) {
1062                 updateFolder(folder.getParent(), true, new Command() {
1063
1064                     @Override
1065                     public void execute() {
1066                         folderTreeSelectionModel.setSelected(folder.getParent(), true);
1067                         updateStatistics();
1068                         if(callback != null) {
1069                             callback.execute();
1070                         }
1071                         pwp.hide();
1072                     }
1073                 }, true);
1074             }
1075
1076             @Override
1077             public void onError(Throwable t) {
1078                 LOG(t);
1079                 setError(t);
1080                 if(t instanceof RestException) {
1081                     if(((RestException) t).getHttpStatusCode() != Response.SC_NOT_FOUND) {
1082                         displayError("Unable to delete folder: " + ((RestException) t).getHttpStatusText());
1083                     }
1084                     else {
1085                         onSuccess(null);
1086                     }
1087                 }
1088                 else {
1089                     displayError("System error unable to delete folder: " + t.getMessage());
1090                 }
1091                 pwp.hide();
1092             }
1093         };
1094         deleteFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1095         Scheduler.get().scheduleDeferred(deleteFolder);
1096     }
1097
1098     public FolderTreeView getFolderTreeView() {
1099         return folderTreeView;
1100     }
1101
1102     public void copyFiles(final Iterator<File> iter, final String targetUsername, final String targetUri, final Command callback) {
1103         if(iter.hasNext()) {
1104             File file = iter.next();
1105             String path = targetUri + "/" + file.getName();
1106             PutRequest copyFile = new PutRequest(getApiPath(), targetUsername, path) {
1107                 @Override
1108                 public void onSuccess(Resource result) {
1109                     copyFiles(iter, targetUsername, targetUri, callback);
1110                 }
1111
1112                 @Override
1113                 public void onError(Throwable t) {
1114                     LOG(t);
1115                     setError(t);
1116                     if(t instanceof RestException) {
1117                         displayError("Unable to copy file: " + ((RestException) t).getHttpStatusText());
1118                     }
1119                     else {
1120                         displayError("System error unable to copy file: " + t.getMessage());
1121                     }
1122                 }
1123
1124                 @Override
1125                 protected void onUnauthorized(Response response) {
1126                     sessionExpired();
1127                 }
1128             };
1129             copyFile.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1130             copyFile.setHeader(Const.X_COPY_FROM, URL.encodePathSegment(file.getUri()));
1131             if(!file.getOwnerID().equals(targetUsername)) {
1132                 copyFile.setHeader(Const.X_SOURCE_ACCOUNT, URL.encodePathSegment(file.getOwnerID()));
1133             }
1134             copyFile.setHeader(Const.CONTENT_TYPE, file.getContentType());
1135             Scheduler.get().scheduleDeferred(copyFile);
1136         }
1137         else if(callback != null) {
1138             callback.execute();
1139         }
1140     }
1141
1142     public void copyFolder(final Folder f, final String targetUsername, final String targetUri, boolean move, final Command callback) {
1143         String path = targetUri + "?delimiter=/";
1144         PutRequest copyFolder = new PutRequest(getApiPath(), targetUsername, path) {
1145             @Override
1146             public void onSuccess(Resource result) {
1147                 if(callback != null) {
1148                     callback.execute();
1149                 }
1150             }
1151
1152             @Override
1153             public void onError(Throwable t) {
1154                 LOG(t);
1155                 setError(t);
1156                 if(t instanceof RestException) {
1157                     displayError("Unable to copy folder: " + ((RestException) t).getHttpStatusText());
1158                 }
1159                 else {
1160                     displayError("System error copying folder: " + t.getMessage());
1161                 }
1162             }
1163
1164             @Override
1165             protected void onUnauthorized(Response response) {
1166                 sessionExpired();
1167             }
1168         };
1169         copyFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1170         copyFolder.setHeader(Const.ACCEPT, "*/*");
1171         copyFolder.setHeader(Const.CONTENT_LENGTH, "0");
1172         copyFolder.setHeader(Const.CONTENT_TYPE, "application/directory");
1173         if(!f.getOwnerID().equals(targetUsername)) {
1174             copyFolder.setHeader(Const.X_SOURCE_ACCOUNT, f.getOwnerID());
1175         }
1176         if(move) {
1177             copyFolder.setHeader(Const.X_MOVE_FROM, URL.encodePathSegment(f.getUri()));
1178         }
1179         else {
1180             copyFolder.setHeader(Const.X_COPY_FROM, URL.encodePathSegment(f.getUri()));
1181         }
1182         Scheduler.get().scheduleDeferred(copyFolder);
1183     }
1184
1185     public void addSelectionModel(@SuppressWarnings("rawtypes") SingleSelectionModel model) {
1186         selectionModels.add(model);
1187     }
1188
1189     public OtherSharedTreeView getOtherSharedTreeView() {
1190         return otherSharedTreeView;
1191     }
1192
1193     public void updateTrash(boolean showFiles, Command callback) {
1194         updateFolder(trash, showFiles, callback, true);
1195     }
1196
1197     public void updateGroupsNode() {
1198         groupTreeView.updateGroupNode(null);
1199     }
1200
1201     public Group addGroup(String groupname) {
1202         Group newGroup = new Group(groupname);
1203         account.addGroup(newGroup);
1204         groupTreeView.updateGroupNode(null);
1205         return newGroup;
1206     }
1207
1208     public void removeGroup(Group group) {
1209         account.removeGroup(group);
1210         updateGroupsNode();
1211     }
1212
1213     public TreeView getSelectedTree() {
1214         return selectedTree;
1215     }
1216
1217     public void setSelectedTree(TreeView selected) {
1218         selectedTree = selected;
1219     }
1220
1221     public Folder getSelection() {
1222         if(selectedTree != null) {
1223             return selectedTree.getSelection();
1224         }
1225         return null;
1226     }
1227
1228     public void showFolderStatistics(int folderFileCount) {
1229         numOfFiles.setHTML(String.valueOf(folderFileCount));
1230     }
1231
1232     public GroupTreeView getGroupTreeView() {
1233         return groupTreeView;
1234     }
1235
1236     public void sessionExpired() {
1237         new SessionExpiredDialog(this).center();
1238     }
1239
1240     public void updateRootFolder(Command callback) {
1241         updateFolder(account.getPithos(), false, callback, true);
1242     }
1243
1244     void createMySharedTree() {
1245         LOG("Pithos::createMySharedTree()");
1246         mysharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1247         mysharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1248             @Override
1249             public void onSelectionChange(SelectionChangeEvent event) {
1250                 if(mysharedTreeSelectionModel.getSelectedObject() != null) {
1251                     deselectOthers(mysharedTreeView, mysharedTreeSelectionModel);
1252                     upload.setEnabled(false);
1253                     disableUploadArea();
1254                     updateSharedFolder(mysharedTreeSelectionModel.getSelectedObject(), true);
1255                     showRelevantToolbarButtons();
1256                 }
1257                 else {
1258                     if(getSelectedTree().equals(mysharedTreeView)) {
1259                         setSelectedTree(null);
1260                     }
1261                     if(getSelectedTree() == null) {
1262                         showRelevantToolbarButtons();
1263                     }
1264                 }
1265             }
1266         });
1267         selectionModels.add(mysharedTreeSelectionModel);
1268         mysharedTreeViewModel = new MysharedTreeViewModel(Pithos.this, mysharedTreeSelectionModel);
1269         mysharedTreeViewModel.initialize(new Command() {
1270
1271             @Override
1272             public void execute() {
1273                 mysharedTreeView = new MysharedTreeView(mysharedTreeViewModel);
1274                 trees.insert(mysharedTreeView, 2);
1275                 treeViews.add(mysharedTreeView);
1276                 createOtherSharedTree();
1277             }
1278         });
1279     }
1280
1281     void createOtherSharedTree() {
1282         LOG("Pithos::createOtherSharedTree()");
1283         otherSharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1284         otherSharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1285             @Override
1286             public void onSelectionChange(SelectionChangeEvent event) {
1287                 if(otherSharedTreeSelectionModel.getSelectedObject() != null) {
1288                     deselectOthers(otherSharedTreeView, otherSharedTreeSelectionModel);
1289                     applyPermissions(otherSharedTreeSelectionModel.getSelectedObject());
1290                     updateOtherSharedFolder(otherSharedTreeSelectionModel.getSelectedObject(), true, null);
1291                     showRelevantToolbarButtons();
1292                 }
1293                 else {
1294                     if(getSelectedTree().equals(otherSharedTreeView)) {
1295                         setSelectedTree(null);
1296                     }
1297                     if(getSelectedTree() == null) {
1298                         showRelevantToolbarButtons();
1299                     }
1300                 }
1301             }
1302         });
1303         selectionModels.add(otherSharedTreeSelectionModel);
1304         otherSharedTreeViewModel = new OtherSharedTreeViewModel(Pithos.this, otherSharedTreeSelectionModel);
1305         LOG("Pithos::createOtherSharedTree(), initializing otherSharedTreeViewModel with a callback");
1306         otherSharedTreeViewModel.initialize(new Command() {
1307             @Override
1308             public void execute() {
1309                 otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel);
1310                 trees.insert(otherSharedTreeView, 1);
1311                 treeViews.add(otherSharedTreeView);
1312                 scheduleResfresh();
1313             }
1314         });
1315     }
1316
1317     public String getErrorData() {
1318         final StringBuilder sb = new StringBuilder();
1319         final String NL = Const.NL;
1320         Throwable t = this.error;
1321         while(t != null) {
1322             sb.append(t.toString());
1323             sb.append(NL);
1324             StackTraceElement[] traces = t.getStackTrace();
1325             for(StackTraceElement trace : traces) {
1326                 sb.append("  [");
1327                 sb.append(trace.getClassName());
1328                 sb.append("::");
1329                 sb.append(trace.getMethodName());
1330                 sb.append("() at ");
1331                 sb.append(trace.getFileName());
1332                 sb.append(":");
1333                 sb.append(trace.getLineNumber());
1334                 sb.append("]");
1335                 sb.append(NL);
1336             }
1337             t = t.getCause();
1338         }
1339
1340         return sb.toString();
1341     }
1342
1343     public void setError(Throwable t) {
1344         error = t;
1345         LOG(t);
1346     }
1347
1348     public void showRelevantToolbarButtons() {
1349         toolbar.showRelevantButtons();
1350     }
1351
1352     public FileUploadDialog getFileUploadDialog() {
1353         if(fileUploadDialog == null) {
1354             fileUploadDialog = new FileUploadDialog(this);
1355         }
1356         return fileUploadDialog;
1357     }
1358
1359     public void hideUploadIndicator() {
1360         upload.removeStyleName("pithos-uploadButton-loading");
1361         upload.setTitle("");
1362     }
1363
1364     public void showUploadIndicator() {
1365         upload.addStyleName("pithos-uploadButton-loading");
1366         upload.setTitle("Upload in progress. Click for details.");
1367     }
1368
1369     public void scheduleFolderHeadCommand(final Folder folder, final Command callback) {
1370         if(folder == null) {
1371             if(callback != null) {
1372                 callback.execute();
1373             }
1374         }
1375         else {
1376             HeadRequest<Folder> headFolder = new HeadRequest<Folder>(Folder.class, getApiPath(), folder.getOwnerID(), folder.getUri(), folder) {
1377
1378                 @Override
1379                 public void onSuccess(Folder _result) {
1380                     if(callback != null) {
1381                         callback.execute();
1382                     }
1383                 }
1384
1385                 @Override
1386                 public void onError(Throwable t) {
1387                     if(t instanceof RestException) {
1388                         if(((RestException) t).getHttpStatusCode() == Response.SC_NOT_FOUND) {
1389                             final String path = folder.getUri();
1390                             PutRequest newFolder = new PutRequest(getApiPath(), folder.getOwnerID(), path) {
1391                                 @Override
1392                                 public void onSuccess(Resource _result) {
1393                                     scheduleFolderHeadCommand(folder, callback);
1394                                 }
1395
1396                                 @Override
1397                                 public void onError(Throwable _t) {
1398                                     setError(_t);
1399                                     if(_t instanceof RestException) {
1400                                         displayError("Unable to create folder: " + ((RestException) _t).getHttpStatusText());
1401                                     }
1402                                     else {
1403                                         displayError("System error creating folder: " + _t.getMessage());
1404                                     }
1405                                 }
1406
1407                                 @Override
1408                                 protected void onUnauthorized(Response response) {
1409                                     sessionExpired();
1410                                 }
1411                             };
1412                             newFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1413                             newFolder.setHeader(Const.CONTENT_TYPE, "application/folder");
1414                             newFolder.setHeader(Const.ACCEPT, "*/*");
1415                             newFolder.setHeader(Const.CONTENT_LENGTH, "0");
1416                             Scheduler.get().scheduleDeferred(newFolder);
1417                         }
1418                         else if(((RestException) t).getHttpStatusCode() == Response.SC_FORBIDDEN) {
1419                             onSuccess(folder);
1420                         }
1421                         else {
1422                             displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
1423                         }
1424                     }
1425                     else {
1426                         displayError("System error heading folder: " + t.getMessage());
1427                     }
1428
1429                     LOG("Error heading folder", t);
1430                     setError(t);
1431                 }
1432
1433                 @Override
1434                 protected void onUnauthorized(Response response) {
1435                     sessionExpired();
1436                 }
1437             };
1438             headFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1439             Scheduler.get().scheduleDeferred(headFolder);
1440         }
1441     }
1442
1443     public void scheduleFileHeadCommand(File f, final Command callback) {
1444         HeadRequest<File> headFile = new HeadRequest<File>(File.class, getApiPath(), f.getOwnerID(), f.getUri(), f) {
1445
1446             @Override
1447             public void onSuccess(File _result) {
1448                 if(callback != null) {
1449                     callback.execute();
1450                 }
1451             }
1452
1453             @Override
1454             public void onError(Throwable t) {
1455                 LOG("Error heading file", t);
1456                 setError(t);
1457                 if(t instanceof RestException) {
1458                     displayError("Error heading file: " + ((RestException) t).getHttpStatusText());
1459                 }
1460                 else {
1461                     displayError("System error heading file: " + t.getMessage());
1462                 }
1463             }
1464
1465             @Override
1466             protected void onUnauthorized(Response response) {
1467                 sessionExpired();
1468             }
1469         };
1470         headFile.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1471         Scheduler.get().scheduleDeferred(headFile);
1472     }
1473
1474     public boolean isMySharedSelected() {
1475         return getSelectedTree().equals(getMySharedTreeView());
1476     }
1477
1478     private Folder getUploadFolder() {
1479         if(folderTreeView.equals(getSelectedTree()) || otherSharedTreeView.equals(getSelectedTree())) {
1480             return getSelection();
1481         }
1482         return null;
1483     }
1484
1485     private void updateUploadFolder() {
1486         updateUploadFolder(null);
1487     }
1488
1489     private void updateUploadFolder(final JsArrayString urls) {
1490         if(folderTreeView.equals(getSelectedTree()) || otherSharedTreeView.equals(getSelectedTree())) {
1491             Folder f = getSelection();
1492             if(getSelectedTree().equals(getFolderTreeView())) {
1493                 updateFolder(f, true, new Command() {
1494
1495                     @Override
1496                     public void execute() {
1497                         updateStatistics();
1498                         if(urls != null) {
1499                             selectUploadedFiles(urls);
1500                         }
1501                     }
1502                 }, false);
1503             }
1504             else {
1505                 updateOtherSharedFolder(f, true, null);
1506             }
1507         }
1508     }
1509
1510     public native void disableUploadArea() /*-{
1511       var uploader = $wnd.$("#uploader").pluploadQueue();
1512       var dropElm = $wnd.document.getElementById('rightPanel');
1513       $wnd.plupload.removeAllEvents(dropElm, uploader.id);
1514     }-*/;
1515
1516     public native void enableUploadArea() /*-{
1517       var uploader = $wnd.$("#uploader").pluploadQueue();
1518       var dropElm = $wnd.document.getElementById('rightPanel');
1519       $wnd.plupload.removeAllEvents(dropElm, uploader.id);
1520       if (uploader.runtime == 'html5') {
1521         uploader.settings.drop_element = 'rightPanel';
1522         uploader.trigger('PostInit');
1523       }
1524     }-*/;
1525
1526     public void showUploadAlert(int nOfFiles) {
1527         if(uploadAlert == null) {
1528             uploadAlert = new UploadAlert(this, nOfFiles);
1529         }
1530         if(!uploadAlert.isShowing()) {
1531             uploadAlert.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
1532
1533                 @Override
1534                 public void setPosition(int offsetWidth, int offsetHeight) {
1535                     uploadAlert.setPopupPosition((Window.getClientWidth() - offsetWidth) / 2, statusPanel.getAbsoluteTop() - offsetHeight);
1536                 }
1537             });
1538         }
1539         uploadAlert.setNumOfFiles(nOfFiles);
1540     }
1541
1542     public void hideUploadAlert() {
1543         if(uploadAlert != null && uploadAlert.isShowing()) {
1544             uploadAlert.hide();
1545         }
1546     }
1547
1548     public void selectUploadedFiles(JsArrayString urls) {
1549         List<String> selectedUrls = new ArrayList<String>();
1550         for(int i = 0; i < urls.length(); i++) {
1551             selectedUrls.add(urls.get(i));
1552         }
1553         fileList.selectByUrl(selectedUrls);
1554     }
1555
1556     public void purgeContainer(final Folder container) {
1557         String path = "/" + container.getName() + "?delimiter=/";
1558         DeleteRequest delete = new DeleteRequest(getApiPath(), getUserID(), path) {
1559
1560             @Override
1561             protected void onUnauthorized(Response response) {
1562                 sessionExpired();
1563             }
1564
1565             @Override
1566             public void onSuccess(Resource result) {
1567                 updateFolder(container, true, null, true);
1568             }
1569
1570             @Override
1571             public void onError(Throwable t) {
1572                 LOG("Error deleting trash", t);
1573                 setError(t);
1574                 if(t instanceof RestException) {
1575                     displayError("Error deleting trash: " + ((RestException) t).getHttpStatusText());
1576                 }
1577                 else {
1578                     displayError("System error deleting trash: " + t.getMessage());
1579                 }
1580             }
1581         };
1582         delete.setHeader(Const.X_AUTH_TOKEN, getUserToken());
1583         Scheduler.get().scheduleDeferred(delete);
1584     }
1585 }