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