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