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