Finished files section of the new left pane
[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     private 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         groupTreeViewModel = new GroupTreeViewModel(this);
369         groupTreeView = new GroupTreeView(groupTreeViewModel);
370         treeViews.add(groupTreeView);
371         
372         trees = new VerticalPanel();
373         trees.setWidth("100%");
374         trees.setSpacing(5);
375         
376 //        HorizontalPanel treeHeader = new HorizontalPanel();
377 //        treeHeader.addStyleName("pithos-treeHeader");
378 //        treeHeader.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
379 //        treeHeader.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
380 //        treeHeader.add(statistics);
381 //        treeHeader.setCellHorizontalAlignment(statistics, HasHorizontalAlignment.ALIGN_LEFT);
382 //        trees.add(treeHeader);
383
384         trees.add(folderTreeView);
385         
386         HorizontalPanel separator = new HorizontalPanel();
387         separator.addStyleName("pithos-statisticsSeparator");
388         trees.add(separator);
389         
390         HorizontalPanel statistics = new HorizontalPanel();
391             statistics.addStyleName("pithos-statistics");
392             statistics.add(new HTML("Used:&nbsp;"));
393             usedBytes = new HTML();
394             statistics.add(usedBytes);
395             statistics.add(new HTML("&nbsp;of&nbsp;"));
396             totalBytes = new HTML();
397             statistics.add(totalBytes);
398             statistics.add(new HTML("&nbsp;("));
399             usedPercent = new HTML();
400             statistics.add(usedPercent);
401             statistics.add(new HTML(")"));
402         trees.add(statistics);
403         
404         trees.add(groupTreeView);
405         // Add the left and right panels to the split panel.
406         splitPanel.setLeftWidget(trees);
407         splitPanel.setRightWidget(inner);
408         splitPanel.setSplitPosition("219px");
409         splitPanel.setSize("100%", "100%");
410         splitPanel.addStyleName("pithos-splitPanel");
411         splitPanel.setWidth(contentWidth);
412         outer.add(splitPanel);
413         outer.setCellHorizontalAlignment(splitPanel, HasHorizontalAlignment.ALIGN_CENTER);
414
415         if (!bareContent) {
416                 statusPanel = new StatusPanel();
417                 statusPanel.setWidth("100%");
418                 outer.add(statusPanel);
419                 outer.setCellHorizontalAlignment(statusPanel, HasHorizontalAlignment.ALIGN_CENTER);
420         }
421         else
422                 splitPanel.addStyleName("pithos-splitPanel-noframe");
423
424         // Hook the window resize event, so that we can adjust the UI.
425         Window.addResizeHandler(this);
426         // Clear out the window's built-in margin, because we want to take
427         // advantage of the entire client area.
428         Window.setMargin("0px");
429         // Finally, add the outer panel to the RootPanel, so that it will be
430         // displayed.
431         RootPanel.get().add(outer);
432         // Call the window resized handler to get the initial sizes setup. Doing
433         // this in a deferred command causes it to occur after all widgets'
434         // sizes have been computed by the browser.
435         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
436
437             @Override
438             public void execute() {
439                 onWindowResized(Window.getClientHeight());
440             }
441         });
442
443         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
444             @Override
445             public void execute() {
446                 fetchAccount(new Command() {
447                                         
448                                         @Override
449                                         public void execute() {
450                                 if (!account.hasHomeContainer())
451                                     createHomeContainer(account, this);
452                                 else if (!account.hasTrashContainer())
453                                         createTrashContainer(this);
454                                 else {
455                                         for (Folder f : account.getContainers())
456                                                 if (f.getName().equals(Pithos.TRASH_CONTAINER)) {
457                                                         trash = f;
458                                                         break;
459                                                 }
460                                     folderTreeViewModel.initialize(account, new Command() {
461                                                                 
462                                                                 @Override
463                                                                 public void execute() {
464                                                     createMySharedTree();
465                                                                 }
466                                                         });
467                                     groupTreeViewModel.initialize();
468                                     showStatistics();
469                                 }
470                                         }
471                                 });
472             }
473         });
474         
475         Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
476                         
477                         @Override
478                         public boolean execute() {
479                                 Folder f = getSelection();
480                                 if (f != null) {
481                                         if (getSelectedTree().equals(folderTreeView))
482                                                 updateFolder(f, true, null, false);
483                                         else if (getSelectedTree().equals(mysharedTreeView))
484                                                 updateSharedFolder(f, true);
485                                 }
486                                 return true;
487                         }
488                 }, 3000);
489     }
490
491     public void applyPermissions(Folder f) {
492         if (f != null) {
493                 if (f.isInTrash())
494                         upload.setEnabled(false);
495                 else {
496                         Boolean[] perms = f.getPermissions().get(username);
497                         if (f.getOwner().equals(username) || (perms != null && perms[1] != null && perms[1])) {
498                                 upload.setEnabled(true);
499                         }
500                         else
501                                 upload.setEnabled(false);
502                 }
503         }
504         else
505                 upload.setEnabled(false);
506         }
507
508         @SuppressWarnings({ "rawtypes", "unchecked" })
509         public void deselectOthers(TreeView _selectedTree, SingleSelectionModel model) {
510         selectedTree = _selectedTree;
511         
512         for (Composite c : treeViews)
513                 if (c.equals(selectedTree))
514                         c.addStyleName("cellTreeWidget-selectedTree");
515                 else
516                         c.removeStyleName("cellTreeWidget-selectedTree");
517         
518         for (SingleSelectionModel s : selectionModels)
519             if (!s.equals(model) && s.getSelectedObject() != null)
520                 s.setSelected(s.getSelectedObject(), false);
521     }
522
523     public void showFiles(final Folder f) {
524         Set<File> files = f.getFiles();
525         showFiles(files);
526     }
527
528     public void showFiles(Set<File> files) {
529         fileList.setFiles(new ArrayList<File>(files));
530     }
531
532     /**
533          * Parse and store the user credentials to the appropriate fields.
534          */
535         private boolean parseUserCredentials() {
536         username = Window.Location.getParameter("user");
537         token = Window.Location.getParameter("token");
538         Configuration conf = (Configuration) GWT.create(Configuration.class);
539                 Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
540         if (username == null || username.length() == 0 || token == null || token.length() == 0) {
541             String cookie = otherProperties.get("authCookie");
542             String auth = Cookies.getCookie(cookie);
543             if (auth == null) {
544                 authenticateUser();
545                 return false;
546             }
547             if (auth.startsWith("\""))
548                 auth = auth.substring(1);
549             if (auth.endsWith("\""))
550                 auth = auth.substring(0, auth.length() - 1);
551                         String[] authSplit = auth.split("\\" + conf.cookieSeparator(), 2);
552                         if (authSplit.length != 2) {
553                             authenticateUser();
554                             return false;
555                         }
556                         username = authSplit[0];
557                         token = authSplit[1];
558         }
559         else
560                 Cookies.setCookie(otherProperties.get("authCookie"), username + conf.cookieSeparator() + token, null, "", "/", false);
561
562         String gotoUrl = Window.Location.getParameter("goto");
563                 if (gotoUrl != null && gotoUrl.length() > 0) {
564                         Window.Location.assign(gotoUrl);
565                         return false;
566                 }
567                 return true;
568     }
569
570     /**
571          * Redirect the user to the login page for authentication.
572          */
573         protected void authenticateUser() {
574                 Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
575         Window.Location.assign(otherProperties.get("loginUrl") + Window.Location.getHref());
576         }
577
578         protected void fetchAccount(final Command callback) {
579         String path = "?format=json";
580
581         GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getApiPath(), username, path) {
582             @Override
583             public void onSuccess(AccountResource _result) {
584                 account = _result;
585                 if (callback != null)
586                         callback.execute();
587             }
588
589             @Override
590             public void onError(Throwable t) {
591                 GWT.log("Error getting account", t);
592                                 setError(t);
593                 if (t instanceof RestException)
594                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
595                 else
596                     displayError("System error fetching user data: " + t.getMessage());
597             }
598
599                         @Override
600                         protected void onUnauthorized(Response response) {
601                                 sessionExpired();
602                         }
603         };
604         getAccount.setHeader("X-Auth-Token", token);
605         Scheduler.get().scheduleDeferred(getAccount);
606     }
607
608     public void updateStatistics() {
609         HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getApiPath(), username, "", account) {
610
611                         @Override
612                         public void onSuccess(AccountResource _result) {
613                                 showStatistics();
614                         }
615
616                         @Override
617                         public void onError(Throwable t) {
618                 GWT.log("Error getting account", t);
619                                 setError(t);
620                 if (t instanceof RestException)
621                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
622                 else
623                     displayError("System error fetching user data: " + t.getMessage());
624                         }
625
626                         @Override
627                         protected void onUnauthorized(Response response) {
628                                 sessionExpired();
629                         }
630                 };
631                 headAccount.setHeader("X-Auth-Token", token);
632                 Scheduler.get().scheduleDeferred(headAccount);
633         }
634
635         protected void showStatistics() {
636         usedBytes.setHTML(String.valueOf(account.getFileSizeAsString()));
637         totalBytes.setHTML(String.valueOf(account.getQuotaAsString()));
638         NumberFormat nf = NumberFormat.getPercentFormat();
639         usedPercent.setHTML(nf.format(account.getUsedPercentage()));
640         }
641
642         protected void createHomeContainer(final AccountResource _account, final Command callback) {
643         String path = "/" + Pithos.HOME_CONTAINER;
644         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
645             @Override
646             public void onSuccess(Resource result) {
647                 if (!_account.hasTrashContainer())
648                         createTrashContainer(callback);
649                 else
650                         fetchAccount(callback);
651             }
652
653             @Override
654             public void onError(Throwable t) {
655                 GWT.log("Error creating pithos", t);
656                                 setError(t);
657                 if (t instanceof RestException)
658                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
659                 else
660                     displayError("System error Error creating pithos: " + t.getMessage());
661             }
662
663                         @Override
664                         protected void onUnauthorized(Response response) {
665                                 sessionExpired();
666                         }
667         };
668         createPithos.setHeader("X-Auth-Token", getToken());
669         Scheduler.get().scheduleDeferred(createPithos);
670     }
671
672     protected void createTrashContainer(final Command callback) {
673         String path = "/" + Pithos.TRASH_CONTAINER;
674         PutRequest createPithos = new PutRequest(getApiPath(), getUsername(), path) {
675             @Override
676             public void onSuccess(Resource result) {
677                         fetchAccount(callback);
678             }
679
680             @Override
681             public void onError(Throwable t) {
682                 GWT.log("Error creating pithos", t);
683                                 setError(t);
684                 if (t instanceof RestException)
685                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
686                 else
687                     displayError("System error Error creating pithos: " + t.getMessage());
688             }
689
690                         @Override
691                         protected void onUnauthorized(Response response) {
692                                 sessionExpired();
693                         }
694         };
695         createPithos.setHeader("X-Auth-Token", getToken());
696         Scheduler.get().scheduleDeferred(createPithos);
697     }
698
699     /**
700          * Creates an HTML fragment that places an image & caption together, for use
701          * in a group header.
702          *
703          * @param imageProto an image prototype for an image
704          * @param caption the group caption
705          * @return the header HTML fragment
706          */
707         private String createHeaderHTML(AbstractImagePrototype imageProto, String caption) {
708                 String captionHTML = "<table class='caption' cellpadding='0' " 
709                 + "cellspacing='0'>" + "<tr><td class='lcaption'>" + imageProto.getHTML() 
710                 + "</td><td id =" + caption +" class='rcaption'><b style='white-space:nowrap'>&nbsp;" 
711                 + caption + "</b></td></tr></table>";
712                 return captionHTML;
713         }
714
715         protected void onWindowResized(int height) {
716                 // Adjust the split panel to take up the available room in the window.
717                 int newHeight = height - splitPanel.getAbsoluteTop();
718                 if (newHeight < 1)
719                         newHeight = 1;
720                 splitPanel.setHeight("" + newHeight);
721                 inner.setHeight("" + newHeight);
722         }
723
724         @Override
725         public void onResize(ResizeEvent event) {
726                 int height = event.getHeight();
727                 onWindowResized(height);
728         }
729
730         /**
731          * Display an error message.
732          *
733          * @param msg the message to display
734          */
735         public void displayError(String msg) {
736                 messagePanel.displayError(msg);
737         }
738
739         /**
740          * Display a warning message.
741          *
742          * @param msg the message to display
743          */
744         public void displayWarning(String msg) {
745                 messagePanel.displayWarning(msg);
746         }
747
748         /**
749          * Display an informational message.
750          *
751          * @param msg the message to display
752          */
753         public void displayInformation(String msg) {
754                 messagePanel.displayInformation(msg);
755         }
756
757         /**
758          * Retrieve the fileList.
759          *
760          * @return the fileList
761          */
762         public FileList getFileList() {
763                 return fileList;
764         }
765
766         /**
767          * Retrieve the topPanel.
768          *
769          * @return the topPanel
770          */
771         TopPanel getTopPanel() {
772                 return topPanel;
773         }
774
775         /**
776          * Retrieve the clipboard.
777          *
778          * @return the clipboard
779          */
780         public Clipboard getClipboard() {
781                 return clipboard;
782         }
783
784         public StatusPanel getStatusPanel() {
785                 return statusPanel;
786         }
787
788         public String getToken() {
789                 return token;
790         }
791
792         public static native void preventIESelection() /*-{
793                 $doc.body.onselectstart = function () { return false; };
794         }-*/;
795
796         public static native void enableIESelection() /*-{
797                 if ($doc.body.onselectstart != null)
798                 $doc.body.onselectstart = null;
799         }-*/;
800
801         /**
802          * @return the absolute path of the API root URL
803          */
804         public String getApiPath() {
805                 Configuration conf = (Configuration) GWT.create(Configuration.class);
806                 return conf.apiPath();
807         }
808
809         /**
810          * History support for folder navigation
811          * adds a new browser history entry
812          *
813          * @param key
814          */
815         public void updateHistory(String key){
816 //              Replace any whitespace of the initial string to "+"
817 //              String result = key.replaceAll("\\s","+");
818 //              Add a new browser history entry.
819 //              History.newItem(result);
820                 History.newItem(key);
821         }
822
823     public void deleteFolder(final Folder folder, final Command callback) {
824         String path = getApiPath() + folder.getOwner() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(folder.getPrefix()) + "&t=" + System.currentTimeMillis();
825         RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
826         builder.setHeader("X-Auth-Token", getToken());
827         try {
828             builder.sendRequest("", new RequestCallback() {
829                 @Override
830                 public void onResponseReceived(Request request, Response response) {
831                     if (response.getStatusCode() == Response.SC_OK) {
832                         JSONValue json = JSONParser.parseStrict(response.getText());
833                         JSONArray array = json.isArray();
834                         int i = 0;
835                         if (array != null) {
836                             deleteObject(folder, i, array, callback);
837                         }
838                     }
839                 }
840
841                 @Override
842                 public void onError(Request request, Throwable exception) {
843                         setError(exception);
844                     displayError("System error unable to delete folder: " + exception.getMessage());
845                 }
846             });
847         }
848         catch (RequestException e) {
849         }
850     }
851
852     void deleteObject(final Folder folder, final int i, final JSONArray array, final Command callback) {
853         if (i < array.size()) {
854             JSONObject o = array.get(i).isObject();
855             if (o != null && !o.containsKey("subdir")) {
856                 JSONString name = o.get("name").isString();
857                 String path = "/" + folder.getContainer() + "/" + name.stringValue();
858                 DeleteRequest delete = new DeleteRequest(getApiPath(), folder.getOwner(), URL.encode(path)) {
859                     @Override
860                     public void onSuccess(Resource result) {
861                         deleteObject(folder, i + 1, array, callback);
862                     }
863
864                     @Override
865                     public void onError(Throwable t) {
866                         GWT.log("", t);
867                                                 setError(t);
868                         displayError("System error unable to delete folder: " + t.getMessage());
869                     }
870
871                                 @Override
872                                 protected void onUnauthorized(Response response) {
873                                         sessionExpired();
874                                 }
875                 };
876                 delete.setHeader("X-Auth-Token", getToken());
877                 Scheduler.get().scheduleDeferred(delete);
878             }
879             else if (o != null) {
880                 String subdir = o.get("subdir").isString().stringValue();
881                 subdir = subdir.substring(0, subdir.length() - 1);
882                 String path = getApiPath() + getUsername() + "/" + folder.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(subdir) + "&t=" + System.currentTimeMillis();
883                 RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, path);
884                 builder.setHeader("X-Auth-Token", getToken());
885                 try {
886                     builder.sendRequest("", new RequestCallback() {
887                         @Override
888                         public void onResponseReceived(Request request, Response response) {
889                             if (response.getStatusCode() == Response.SC_OK) {
890                                 JSONValue json = JSONParser.parseStrict(response.getText());
891                                 JSONArray array2 = json.isArray();
892                                 if (array2 != null) {
893                                     int l = array.size();
894                                     for (int j=0; j<array2.size(); j++) {
895                                         array.set(l++, array2.get(j));
896                                     }
897                                 }
898                                 deleteObject(folder, i + 1, array, callback);
899                             }
900                         }
901
902                         @Override
903                         public void onError(Request request, Throwable exception) {
904                                 setError(exception);
905                             displayError("System error unable to delete folder: " + exception.getMessage());
906                         }
907                     });
908                 }
909                 catch (RequestException e) {
910                 }
911             }
912         }
913         else {
914             String path = folder.getUri();
915             DeleteRequest deleteFolder = new DeleteRequest(getApiPath(), getUsername(), URL.encode(path)) {
916                 @Override
917                 public void onSuccess(Resource result) {
918                     updateFolder(folder.getParent(), true, new Command() {
919                                                 
920                                                 @Override
921                                                 public void execute() {
922                                                         folderTreeSelectionModel.setSelected(folder.getParent(), true);
923                                                         updateStatistics();
924                                                         if (callback != null)
925                                                                 callback.execute();
926                                                 }
927                                         }, true);
928                 }
929
930                 @Override
931                 public void onError(Throwable t) {
932                     GWT.log("", t);
933                                         setError(t);
934                     if (t instanceof RestException) {
935                         if (((RestException) t).getHttpStatusCode() != Response.SC_NOT_FOUND)
936                                 displayError("Unable to delete folder: "+((RestException) t).getHttpStatusText());
937                         else
938                                 onSuccess(null);
939                     }
940                     else
941                         displayError("System error unable to delete folder: " + t.getMessage());
942                 }
943
944                                 @Override
945                                 protected void onUnauthorized(Response response) {
946                                         sessionExpired();
947                                 }
948             };
949             deleteFolder.setHeader("X-Auth-Token", getToken());
950             Scheduler.get().scheduleDeferred(deleteFolder);
951         }
952     }
953
954     public FolderTreeView getFolderTreeView() {
955         return folderTreeView;
956     }
957
958     public void copyFiles(final Iterator<File> iter, final String targetUsername, final String targetUri, final Command callback) {
959         if (iter.hasNext()) {
960             File file = iter.next();
961             String path = targetUri + "/" + file.getName();
962             PutRequest copyFile = new PutRequest(getApiPath(), targetUsername, path) {
963                 @Override
964                 public void onSuccess(Resource result) {
965                     copyFiles(iter, targetUsername, targetUri, callback);
966                 }
967
968                 @Override
969                 public void onError(Throwable t) {
970                     GWT.log("", t);
971                                         setError(t);
972                     if (t instanceof RestException) {
973                         displayError("Unable to copy file: " + ((RestException) t).getHttpStatusText());
974                     }
975                     else
976                         displayError("System error unable to copy file: "+t.getMessage());
977                 }
978
979                                 @Override
980                                 protected void onUnauthorized(Response response) {
981                                         sessionExpired();
982                                 }
983             };
984             copyFile.setHeader("X-Auth-Token", getToken());
985             copyFile.setHeader("X-Copy-From", URL.encodePathSegment(file.getUri()));
986             if (!file.getOwner().equals(targetUsername))
987                 copyFile.setHeader("X-Source-Account", URL.encodePathSegment(file.getOwner()));
988             copyFile.setHeader("Content-Type", file.getContentType());
989             Scheduler.get().scheduleDeferred(copyFile);
990         }
991         else  if (callback != null) {
992             callback.execute();
993         }
994     }
995
996     public void copySubfolders(final Iterator<Folder> iter, final String targetUsername, final String targetUri, final Command callback) {
997         if (iter.hasNext()) {
998             final Folder f = iter.next();
999             copyFolder(f, targetUsername, targetUri, new Command() {
1000                                 
1001                                 @Override
1002                                 public void execute() {
1003                                         copySubfolders(iter, targetUsername, targetUri, callback);
1004                                 }
1005                         });
1006         }
1007         else  if (callback != null) {
1008             callback.execute();
1009         }
1010     }
1011
1012     public void copyFolder(final Folder f, final String targetUsername, final String targetUri, final Command callback) {
1013         String path = targetUri + "/" + f.getName();
1014         PutRequest createFolder = new PutRequest(getApiPath(), targetUsername, path) {
1015             @Override
1016             public void onSuccess(Resource result) {
1017                 GetRequest<Folder> getFolder = new GetRequest<Folder>(Folder.class, getApiPath(), f.getOwner(), "/" + f.getContainer() + "?format=json&delimiter=/&prefix=" + URL.encodeQueryString(f.getPrefix()), f) {
1018
1019                                         @Override
1020                                         public void onSuccess(final Folder _f) {
1021                                 Iterator<File> iter = _f.getFiles().iterator();
1022                                 copyFiles(iter, targetUsername, targetUri + "/" + _f.getName(), new Command() {
1023                                     @Override
1024                                     public void execute() {
1025                                         Iterator<Folder> iterf = _f.getSubfolders().iterator();
1026                                         copySubfolders(iterf, targetUsername, targetUri + "/" + _f.getName(), callback);
1027                                     }
1028                                 });
1029                                         }
1030
1031                                         @Override
1032                                         public void onError(Throwable t) {
1033                                 GWT.log("", t);
1034                                                 setError(t);
1035                                 if (t instanceof RestException) {
1036                                     displayError("Unable to get folder: " + ((RestException) t).getHttpStatusText());
1037                                 }
1038                                 else
1039                                     displayError("System error getting folder: " + t.getMessage());
1040                                         }
1041
1042                                         @Override
1043                                         protected void onUnauthorized(Response response) {
1044                                                 sessionExpired();
1045                                         }
1046                                 };
1047                                 getFolder.setHeader("X-Auth-Token", getToken());
1048                                 Scheduler.get().scheduleDeferred(getFolder);
1049             }
1050
1051             @Override
1052             public void onError(Throwable t) {
1053                 GWT.log("", t);
1054                                 setError(t);
1055                if (t instanceof RestException) {
1056                     displayError("Unable to create folder: " + ((RestException) t).getHttpStatusText());
1057                 }
1058                 else
1059                     displayError("System error creating folder: " + t.getMessage());
1060             }
1061
1062                         @Override
1063                         protected void onUnauthorized(Response response) {
1064                                 sessionExpired();
1065                         }
1066         };
1067         createFolder.setHeader("X-Auth-Token", getToken());
1068         createFolder.setHeader("Accept", "*/*");
1069         createFolder.setHeader("Content-Length", "0");
1070         createFolder.setHeader("Content-Type", "application/folder");
1071         Scheduler.get().scheduleDeferred(createFolder);
1072     }
1073     
1074     public void addSelectionModel(@SuppressWarnings("rawtypes") SingleSelectionModel model) {
1075         selectionModels.add(model);
1076     }
1077
1078         public OtherSharedTreeView getOtherSharedTreeView() {
1079                 return otherSharedTreeView;
1080         }
1081
1082         public void updateTrash(boolean showFiles, Command callback) {
1083                 updateFolder(trash, showFiles, callback, true);
1084         }
1085
1086         public void updateGroupsNode() {
1087                 groupTreeView.updateGroupNode(null);
1088         }
1089
1090         public void addGroup(String groupname) {
1091                 Group newGroup = new Group(groupname);
1092                 account.addGroup(newGroup);
1093                 groupTreeView.updateGroupNode(null);
1094         }
1095
1096         public void removeGroup(Group group) {
1097                 account.removeGroup(group);
1098                 updateGroupsNode();
1099         }
1100
1101         public TreeView getSelectedTree() {
1102                 return selectedTree;
1103         }
1104         
1105         public void setSelectedTree(TreeView selected) {
1106                 selectedTree = selected;
1107         }
1108
1109         public Folder getSelection() {
1110                 return selectedTree.getSelection();
1111         }
1112
1113         public void showFolderStatistics(int folderFileCount) {
1114                 numOfFiles.setHTML(String.valueOf(folderFileCount));
1115         }
1116
1117         public GroupTreeView getGroupTreeView() {
1118                 return groupTreeView;
1119         }
1120
1121         public void sessionExpired() {
1122                 new SessionExpiredDialog(this).center();
1123         }
1124
1125         public void updateRootFolder(Command callback) {
1126                 updateFolder(account.getPithos(), false, callback, true);
1127         }
1128
1129         void createMySharedTree() {
1130                 mysharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1131                 mysharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1132                     @Override
1133                     public void onSelectionChange(SelectionChangeEvent event) {
1134                         if (mysharedTreeSelectionModel.getSelectedObject() != null) {
1135                             deselectOthers(mysharedTreeView, mysharedTreeSelectionModel);
1136                             upload.setEnabled(false);
1137                             updateSharedFolder(mysharedTreeSelectionModel.getSelectedObject(), true);
1138                                         showRelevantToolbarButtons();
1139                         }
1140                                 else {
1141                                         if (getSelectedTree().equals(mysharedTreeView))
1142                                                 setSelectedTree(null);
1143                                         if (getSelectedTree() == null)
1144                                                 showRelevantToolbarButtons();
1145                                 }
1146                     }
1147                 });
1148                 selectionModels.add(mysharedTreeSelectionModel);
1149                 mysharedTreeViewModel = new MysharedTreeViewModel(Pithos.this, mysharedTreeSelectionModel);
1150                 mysharedTreeViewModel.initialize(new Command() {
1151                         
1152                         @Override
1153                         public void execute() {
1154                             mysharedTreeView = new MysharedTreeView(mysharedTreeViewModel);
1155                                 trees.insert(mysharedTreeView, 4);
1156                                 treeViews.add(mysharedTreeView);
1157                                 createOtherSharedTree();
1158                         }
1159                 });
1160         }
1161
1162         void createOtherSharedTree() {
1163                 otherSharedTreeSelectionModel = new SingleSelectionModel<Folder>();
1164                 otherSharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
1165                     @Override
1166                     public void onSelectionChange(SelectionChangeEvent event) {
1167                         if (otherSharedTreeSelectionModel.getSelectedObject() != null) {
1168                             deselectOthers(otherSharedTreeView, otherSharedTreeSelectionModel);
1169                             otherSharedTreeView.addStyleName("cellTreeWidget-selectedTree");
1170                             applyPermissions(otherSharedTreeSelectionModel.getSelectedObject());
1171                             updateOtherSharedFolder(otherSharedTreeSelectionModel.getSelectedObject(), true);
1172                                         showRelevantToolbarButtons();
1173                         }
1174                                 else {
1175                                         if (getSelectedTree().equals(otherSharedTreeView))
1176                                                 setSelectedTree(null);
1177                                         if (getSelectedTree() == null)
1178                                                 showRelevantToolbarButtons();
1179                                 }
1180                     }
1181                 });
1182                 selectionModels.add(otherSharedTreeSelectionModel);
1183                 otherSharedTreeViewModel = new OtherSharedTreeViewModel(Pithos.this, otherSharedTreeSelectionModel);
1184                 otherSharedTreeViewModel.initialize(new Command() {
1185                         
1186                         @Override
1187                         public void execute() {
1188                             otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel);
1189                                 trees.insert(otherSharedTreeView, 4);
1190                                 treeViews.add(otherSharedTreeView);
1191                         }
1192                 });
1193         }
1194
1195         public native void log1(String message)/*-{
1196                 $wnd.console.log(message);
1197         }-*/;
1198
1199         public String getErrorData() {
1200                 if (error != null)
1201                         return error.toString();
1202                 return "";
1203         }
1204         
1205         public void setError(Throwable t) {
1206                 error = t;
1207         }
1208         
1209         public void showRelevantToolbarButtons() {
1210                 toolbar.showRelevantButtons();
1211         }
1212
1213         public FileUploadDialog getFileUploadDialog() {
1214                 if (fileUploadDialog == null)
1215                         fileUploadDialog = new FileUploadDialog(this);
1216                 return fileUploadDialog;
1217         }
1218
1219         public void hideUploadIndicator() {
1220                 upload.removeStyleName("pithos-uploadButton-loading");
1221                 upload.setTitle("");
1222         }
1223         
1224         public void showUploadIndicator() {
1225                 upload.addStyleName("pithos-uploadButton-loading");
1226                 upload.setTitle("Upload in progress. Click for details.");
1227         }
1228
1229         public void scheduleFolderHeadCommand(final Folder folder, final Command callback) {
1230                 if (folder == null) {
1231                         if (callback != null)
1232                                 callback.execute();
1233                 }
1234                 else {
1235                         HeadRequest<Folder> headFolder = new HeadRequest<Folder>(Folder.class, getApiPath(), folder.getOwner(), folder.getUri(), folder) {
1236         
1237                                 @Override
1238                                 public void onSuccess(Folder _result) {
1239                                         if (callback != null)
1240                                                 callback.execute();
1241                                 }
1242         
1243                                 @Override
1244                                 public void onError(Throwable t) {
1245                                 if (t instanceof RestException) {
1246                                         if (((RestException) t).getHttpStatusCode() == Response.SC_NOT_FOUND) {
1247                                 final String path = folder.getUri();
1248                                 PutRequest newFolder = new PutRequest(getApiPath(), folder.getOwner(), path) {
1249                                     @Override
1250                                     public void onSuccess(Resource _result) {
1251                                         scheduleFolderHeadCommand(folder, callback);
1252                                     }
1253         
1254                                     @Override
1255                                     public void onError(Throwable _t) {
1256                                         GWT.log("", _t);
1257                                                                 setError(_t);
1258                                         if(_t instanceof RestException){
1259                                             displayError("Unable to create folder: " + ((RestException) _t).getHttpStatusText());
1260                                         }
1261                                         else
1262                                             displayError("System error creating folder: " + _t.getMessage());
1263                                     }
1264         
1265                                                 @Override
1266                                                 protected void onUnauthorized(Response response) {
1267                                                         sessionExpired();
1268                                                 }
1269                                 };
1270                                 newFolder.setHeader("X-Auth-Token", getToken());
1271                                 newFolder.setHeader("Content-Type", "application/folder");
1272                                 newFolder.setHeader("Accept", "*/*");
1273                                 newFolder.setHeader("Content-Length", "0");
1274                                 Scheduler.get().scheduleDeferred(newFolder);
1275                                         }
1276                                         else
1277                                                 displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
1278                                 }
1279                                 else
1280                                     displayError("System error heading folder: " + t.getMessage());
1281         
1282                                 GWT.log("Error heading folder", t);
1283                                         setError(t);
1284                                 }
1285         
1286                                 @Override
1287                                 protected void onUnauthorized(Response response) {
1288                                         sessionExpired();
1289                                 }
1290                         };
1291                         headFolder.setHeader("X-Auth-Token", getToken());
1292                         Scheduler.get().scheduleDeferred(headFolder);
1293                 }
1294         }
1295
1296         public void scheduleFileHeadCommand(File f, final Command callback) {
1297                 HeadRequest<File> headFile = new HeadRequest<File>(File.class, getApiPath(), f.getOwner(), f.getUri(), f) {
1298
1299                         @Override
1300                         public void onSuccess(File _result) {
1301                                 if (callback != null)
1302                                         callback.execute();
1303                         }
1304
1305                         @Override
1306                         public void onError(Throwable t) {
1307                         GWT.log("Error heading file", t);
1308                                 setError(t);
1309                         if (t instanceof RestException)
1310                             displayError("Error heading file: " + ((RestException) t).getHttpStatusText());
1311                         else
1312                             displayError("System error heading file: " + t.getMessage());
1313                         }
1314
1315                         @Override
1316                         protected void onUnauthorized(Response response) {
1317                                 sessionExpired();
1318                         }
1319                 };
1320                 headFile.setHeader("X-Auth-Token", getToken());
1321                 Scheduler.get().scheduleDeferred(headFile);
1322         }
1323 }