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