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