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