Fixed bug when displaying folder tree after the second level. Now the tree works...
[pithos] / web_client / src / gr / grnet / pithos / web / client / GSS.java
1 /*
2  * Copyright (c) 2011 Greek Research and Technology Network
3  */
4 package gr.grnet.pithos.web.client;
5
6 import com.google.gwt.core.client.Scheduler;
7 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
8 import com.google.gwt.user.client.ui.DockPanel;
9 import com.google.gwt.user.client.ui.HasVerticalAlignment;
10 import com.google.gwt.view.client.ListDataProvider;
11 import gr.grnet.pithos.web.client.clipboard.Clipboard;
12 import gr.grnet.pithos.web.client.commands.GetUserCommand;
13 import gr.grnet.pithos.web.client.foldertree.AccountResource;
14 import gr.grnet.pithos.web.client.foldertree.Folder;
15 import gr.grnet.pithos.web.client.foldertree.FolderTreeView;
16 import gr.grnet.pithos.web.client.foldertree.FolderTreeViewModel;
17 import gr.grnet.pithos.web.client.rest.GetRequest;
18 import gr.grnet.pithos.web.client.rest.RestException;
19 import gr.grnet.pithos.web.client.rest.resource.FileResource;
20 import gr.grnet.pithos.web.client.rest.resource.OtherUserResource;
21 import gr.grnet.pithos.web.client.rest.resource.RestResource;
22 import gr.grnet.pithos.web.client.rest.resource.RestResourceWrapper;
23 import gr.grnet.pithos.web.client.rest.resource.SharedResource;
24 import gr.grnet.pithos.web.client.rest.resource.TrashResource;
25 import gr.grnet.pithos.web.client.rest.resource.UserResource;
26
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33
34 import com.google.gwt.core.client.EntryPoint;
35 import com.google.gwt.core.client.GWT;
36 import com.google.gwt.event.logical.shared.ResizeEvent;
37 import com.google.gwt.event.logical.shared.ResizeHandler;
38 import com.google.gwt.event.logical.shared.SelectionEvent;
39 import com.google.gwt.event.logical.shared.SelectionHandler;
40 import com.google.gwt.http.client.URL;
41 import com.google.gwt.i18n.client.DateTimeFormat;
42 import com.google.gwt.resources.client.ClientBundle;
43 import com.google.gwt.resources.client.ImageResource;
44 import com.google.gwt.user.client.Command;
45 import com.google.gwt.user.client.Cookies;
46 import com.google.gwt.user.client.DOM;
47 import com.google.gwt.user.client.DeferredCommand;
48 import com.google.gwt.user.client.Event;
49 import com.google.gwt.user.client.History;
50 import com.google.gwt.user.client.Window;
51 import com.google.gwt.user.client.ui.AbstractImagePrototype;
52 import com.google.gwt.user.client.ui.DecoratedTabPanel;
53 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
54 import com.google.gwt.user.client.ui.HorizontalSplitPanel;
55 import com.google.gwt.user.client.ui.RootPanel;
56 import com.google.gwt.user.client.ui.TabPanel;
57 import com.google.gwt.user.client.ui.VerticalPanel;
58
59 /**
60  * Entry point classes define <code>onModuleLoad()</code>.
61  */
62 public class GSS implements EntryPoint, ResizeHandler {
63
64         /**
65          * A constant that denotes the completion of an IncrementalCommand.
66          */
67         public static final boolean DONE = false;
68
69         public static final int VISIBLE_FILE_COUNT = 25;
70
71         /**
72          * Instantiate an application-level image bundle. This object will provide
73          * programmatic access to all the images needed by widgets.
74          */
75         private static Images images = (Images) GWT.create(Images.class);
76
77     public String getUsername() {
78         return username;
79     }
80
81     public void setAccount(AccountResource acct) {
82         account = acct;
83     }
84
85     public AccountResource getAccount() {
86         return account;
87     }
88
89     /**
90          * An aggregate image bundle that pulls together all the images for this
91          * application into a single bundle.
92          */
93         public interface Images extends ClientBundle, TopPanel.Images, StatusPanel.Images, FileMenu.Images, EditMenu.Images, SettingsMenu.Images, FilePropertiesDialog.Images, MessagePanel.Images, FileList.Images, Search.Images, CellTreeView.Images {
94
95                 @Source("gr/grnet/pithos/resources/document.png")
96                 ImageResource folders();
97
98                 @Source("gr/grnet/pithos/resources/edit_group_22.png")
99                 ImageResource groups();
100
101                 @Source("gr/grnet/pithos/resources/search.png")
102                 ImageResource search();
103         }
104
105         /**
106          * The single GSS instance.
107          */
108         private static GSS singleton;
109
110         /**
111          * Gets the singleton GSS instance.
112          *
113          * @return the GSS object
114          */
115         public static GSS get() {
116                 if (GSS.singleton == null)
117                         GSS.singleton = new GSS();
118                 return GSS.singleton;
119         }
120
121         /**
122          * The Application Clipboard implementation;
123          */
124         private Clipboard clipboard = new Clipboard();
125
126         private UserResource currentUserResource;
127
128         /**
129          * The top panel that contains the menu bar.
130          */
131         private TopPanel topPanel;
132
133         /**
134          * The panel that contains the various system messages.
135          */
136         private MessagePanel messagePanel = new MessagePanel(GSS.images);
137
138         /**
139          * The bottom panel that contains the status bar.
140          */
141         private StatusPanel statusPanel = null;
142
143         /**
144          * The top right panel that displays the logged in user details
145          */
146         private UserDetailsPanel userDetailsPanel = new UserDetailsPanel();
147
148         /**
149          * The file list widget.
150          */
151         private FileList fileList;
152
153         /**
154          * The tab panel that occupies the right side of the screen.
155          */
156         private TabPanel inner = new DecoratedTabPanel(){
157                 
158                 public void onBrowserEvent(com.google.gwt.user.client.Event event) {
159                         if (DOM.eventGetType(event) == Event.ONCONTEXTMENU){
160                                 if(isFileListShowing()){
161                                         getFileList().showContextMenu(event);
162                                 }
163                         }
164                 };
165         };
166
167         /**
168          * The split panel that will contain the left and right panels.
169          */
170         private HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
171
172         /**
173          * The widget that displays the tree of folders.
174          */
175         
176         private CellTreeView treeView = null;
177         /**
178          * The currently selected item in the application, for use by the Edit menu
179          * commands. Potential types are Folder, File, User and Group.
180          */
181         private Object currentSelection;
182
183
184         /**
185          * The WebDAV password of the current user
186          */
187         private String webDAVPassword;
188
189         public HashMap<String, String> userFullNameMap = new HashMap<String, String>();
190
191     private String username = null;
192
193     /**
194      * The authentication token of the current user.
195      */
196     private String token;
197
198     private FolderTreeViewModel folderTreeViewModel = new FolderTreeViewModel();
199
200     private FolderTreeView folderTreeView = new FolderTreeView(folderTreeViewModel);
201
202     private AccountResource account;
203
204         @Override
205         public void onModuleLoad() {
206                 // Initialize the singleton before calling the constructors of the
207                 // various widgets that might call GSS.get().
208                 singleton = this;
209                 if (parseUserCredentials())
210             initialize();
211         }
212
213     private void initialize() {
214         topPanel = new TopPanel(GSS.images);
215         topPanel.setWidth("100%");
216
217         messagePanel.setWidth("100%");
218         messagePanel.setVisible(false);
219
220         fileList = new FileList(images);
221
222         // Inner contains the various lists.
223         inner.sinkEvents(Event.ONCONTEXTMENU);
224         inner.setAnimationEnabled(true);
225         inner.getTabBar().addStyleName("pithos-MainTabBar");
226         inner.getDeckPanel().addStyleName("pithos-MainTabPanelBottom");
227         inner.add(fileList, createHeaderHTML(AbstractImagePrototype.create(images.folders()), "Files"), true);
228
229         inner.setWidth("100%");
230         inner.selectTab(0);
231
232         inner.addSelectionHandler(new SelectionHandler<Integer>() {
233
234             @Override
235             public void onSelection(SelectionEvent<Integer> event) {
236                 int tabIndex = event.getSelectedItem();
237                 switch (tabIndex) {
238                     case 0:
239                         fileList.updateCurrentlyShowingStats();
240                         break;
241                 }
242             }
243         });
244
245         // Add the left and right panels to the split panel.
246         splitPanel.setLeftWidget(folderTreeView);
247         splitPanel.setRightWidget(inner);
248         splitPanel.setSplitPosition("25%");
249         splitPanel.setSize("100%", "100%");
250         splitPanel.addStyleName("pithos-splitPanel");
251
252         // Create a dock panel that will contain the menu bar at the top,
253         // the shortcuts to the left, the status bar at the bottom and the
254         // right panel taking the rest.
255         VerticalPanel outer = new VerticalPanel();
256         outer.add(topPanel);
257         outer.add(messagePanel);
258         outer.add(splitPanel);
259         statusPanel = new StatusPanel(GSS.images);
260         outer.add(statusPanel);
261         outer.setWidth("100%");
262         outer.setCellHorizontalAlignment(messagePanel, HasHorizontalAlignment.ALIGN_CENTER);
263
264         outer.setSpacing(4);
265
266         // Hook the window resize event, so that we can adjust the UI.
267         Window.addResizeHandler(this);
268         // Clear out the window's built-in margin, because we want to take
269         // advantage of the entire client area.
270         Window.setMargin("0px");
271         // Finally, add the outer panel to the RootPanel, so that it will be
272         // displayed.
273         RootPanel.get().add(outer);
274         // Call the window resized handler to get the initial sizes setup. Doing
275         // this in a deferred command causes it to occur after all widgets'
276         // sizes have been computed by the browser.
277         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
278
279             @Override
280             public void execute() {
281                 onWindowResized(Window.getClientHeight());
282             }
283         });
284
285         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
286             @Override
287             public void execute() {
288                 fetchAccount();
289             }
290         });
291     }
292
293         /**
294          * Parse and store the user credentials to the appropriate fields.
295          */
296         private boolean parseUserCredentials() {
297                 Configuration conf = (Configuration) GWT.create(Configuration.class);
298                 String cookie = conf.authCookie();
299                 String auth = Cookies.getCookie(cookie);
300                 if (auth == null) {
301                         authenticateUser();
302             return false;
303         }
304         else {
305             String[] authSplit = auth.split("\\" + conf.cookieSeparator(), 2);
306             if (authSplit.length != 2) {
307                 authenticateUser();
308                 return false;
309             }
310             else {
311                 username = authSplit[0];
312                 token = authSplit[1];
313                 return true;
314             }
315         }
316         }
317
318     /**
319          * Redirect the user to the login page for authentication.
320          */
321         protected void authenticateUser() {
322                 Configuration conf = (Configuration) GWT.create(Configuration.class);
323
324 //        Window.Location.assign(GWT.getModuleBaseURL() + conf.loginUrl() + "?next=" + Window.Location.getHref());
325         Cookies.setCookie(conf.authCookie(), "demo" + conf.cookieSeparator() + "0000");
326         Window.Location.assign(GWT.getModuleBaseURL() + "GSS.html");
327         }
328
329     private void fetchAccount() {
330         String path = getApiPath() + username + "?format=json";
331
332         GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, path) {
333             @Override
334             public void onSuccess(AccountResource result) {
335                 account = result;
336                 statusPanel.displayStats(account);
337                 folderTreeViewModel.initialize(account);
338             }
339
340             @Override
341             public void onError(Throwable t) {
342                 GWT.log("Error getting account", t);
343                 if (t instanceof RestException)
344                     GSS.get().displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
345                 else
346                     GSS.get().displayError("System error fetching user data: " + t.getMessage());
347             }
348         };
349
350         Scheduler.get().scheduleDeferred(getAccount);
351     }
352
353         /**
354          * Clear the cookie and redirect the user to the logout page.
355          */
356         void logout() {
357                 Configuration conf = (Configuration) GWT.create(Configuration.class);
358                 String cookie = conf.authCookie();
359                 String domain = Window.Location.getHostName();
360                 String path = Window.Location.getPath();
361                 Cookies.setCookie(cookie, "", null, domain, path, false);
362         String baseUrl = GWT.getModuleBaseURL();
363         String homeUrl = baseUrl.substring(0, baseUrl.indexOf(path));
364                 Window.Location.assign(homeUrl + conf.logoutUrl());
365         }
366
367         /**
368          * Creates an HTML fragment that places an image & caption together, for use
369          * in a group header.
370          *
371          * @param imageProto an image prototype for an image
372          * @param caption the group caption
373          * @return the header HTML fragment
374          */
375         private String createHeaderHTML(AbstractImagePrototype imageProto, String caption) {
376                 String captionHTML = "<table class='caption' cellpadding='0' " 
377                 + "cellspacing='0'>" + "<tr><td class='lcaption'>" + imageProto.getHTML() 
378                 + "</td><td id =" + caption +" class='rcaption'><b style='white-space:nowrap'>&nbsp;" 
379                 + caption + "</b></td></tr></table>";
380                 return captionHTML;
381         }
382
383         private void onWindowResized(int height) {
384                 // Adjust the split panel to take up the available room in the window.
385                 int newHeight = height - splitPanel.getAbsoluteTop() - 44;
386                 if (newHeight < 1)
387                         newHeight = 1;
388                 splitPanel.setHeight("" + newHeight);
389                 inner.setHeight("" + newHeight);
390         }
391
392         @Override
393         public void onResize(ResizeEvent event) {
394                 int height = event.getHeight();
395                 onWindowResized(height);
396         }
397
398         public boolean isFileListShowing() {
399                 int tab = inner.getTabBar().getSelectedTab();
400                 if (tab == 0)
401                         return true;
402                 return false;
403         }
404
405         public boolean isSearchResultsShowing() {
406                 int tab = inner.getTabBar().getSelectedTab();
407                 if (tab == 2)
408                         return true;
409                 return false;
410         }
411
412         /**
413          * Make the user list visible.
414          */
415         public void showUserList() {
416                 inner.selectTab(1);
417         }
418
419         /**
420          * Make the file list visible.
421          */
422         public void showFileList() {
423                 fileList.updateFileCache(true /*clear selection*/);
424                 inner.selectTab(0);
425         }
426
427         /**
428          * Make the file list visible.
429          *
430          * @param update
431          */
432         public void showFileList(boolean update) {
433                 if(update){
434                         getTreeView().refreshCurrentNode(true);
435                 }
436                 else{
437                         RestResource currentFolder = getTreeView().getSelection();
438                         if(currentFolder!=null){
439                                 showFileList(currentFolder);
440                 }
441                 }
442
443         }
444         
445         public void showFileList(RestResource r) {
446                 showFileList(r,true);
447         }
448         
449         public void showFileList(RestResource r, boolean clearSelection) {
450                 RestResource currentFolder = r;
451                 if(currentFolder!=null){
452                         List<FileResource> files = null;
453                         if (currentFolder instanceof RestResourceWrapper) {
454                                 RestResourceWrapper folder = (RestResourceWrapper) currentFolder;
455                                 files = folder.getResource().getFiles();
456                         } else if (currentFolder instanceof TrashResource) {
457                                 TrashResource folder = (TrashResource) currentFolder;
458                                 files = folder.getFiles();
459                         }
460                         else if (currentFolder instanceof SharedResource) {
461                                 SharedResource folder = (SharedResource) currentFolder;
462                                 files = folder.getFiles();
463                         }
464                         else if (currentFolder instanceof OtherUserResource) {
465                                 OtherUserResource folder = (OtherUserResource) currentFolder;
466                                 files = folder.getFiles();
467                         }
468                         if (files != null)
469                                 getFileList().setFiles(files);
470                         else
471                                 getFileList().setFiles(new ArrayList<FileResource>());
472                 }
473                 fileList.updateFileCache(clearSelection /*clear selection*/);
474                 inner.selectTab(0);
475         }
476
477         /**
478          * Display the 'loading' indicator.
479          */
480         public void showLoadingIndicator(String message, String path) {
481                 if(path!=null){
482                         String[] split = path.split("/");
483                         message = message +" "+URL.decode(split[split.length-1]);
484                 }
485                 topPanel.getLoading().show(message);
486         }
487
488         /**
489          * Hide the 'loading' indicator.
490          */
491         public void hideLoadingIndicator() {
492                 topPanel.getLoading().hide();
493         }
494
495         /**
496          * A native JavaScript method to reach out to the browser's window and
497          * invoke its resizeTo() method.
498          *
499          * @param x the new width
500          * @param y the new height
501          */
502         public static native void resizeTo(int x, int y) /*-{
503                 $wnd.resizeTo(x,y);
504         }-*/;
505
506         /**
507          * A helper method that returns true if the user's list is currently visible
508          * and false if it is hidden.
509          *
510          * @return true if the user list is visible
511          */
512         public boolean isUserListVisible() {
513                 return inner.getTabBar().getSelectedTab() == 1;
514         }
515
516         /**
517          * Display an error message.
518          *
519          * @param msg the message to display
520          */
521         public void displayError(String msg) {
522                 messagePanel.displayError(msg);
523         }
524
525         /**
526          * Display a warning message.
527          *
528          * @param msg the message to display
529          */
530         public void displayWarning(String msg) {
531                 messagePanel.displayWarning(msg);
532         }
533
534         /**
535          * Display an informational message.
536          *
537          * @param msg the message to display
538          */
539         public void displayInformation(String msg) {
540                 messagePanel.displayInformation(msg);
541         }
542
543         /**
544          * Retrieve the folders.
545          *
546          * @return the folders
547          
548         public Folders getFolders() {
549                 return folders;
550         }*/
551
552         /**
553          * Retrieve the currentSelection.
554          *
555          * @return the currentSelection
556          */
557         public Object getCurrentSelection() {
558                 return currentSelection;
559         }
560
561         /**
562          * Modify the currentSelection.
563          *
564          * @param newCurrentSelection the currentSelection to set
565          */
566         public void setCurrentSelection(Object newCurrentSelection) {
567                 currentSelection = newCurrentSelection;
568         }
569
570         /**
571          * Retrieve the fileList.
572          *
573          * @return the fileList
574          */
575         public FileList getFileList() {
576                 return fileList;
577         }
578
579         /**
580          * Retrieve the topPanel.
581          *
582          * @return the topPanel
583          */
584         TopPanel getTopPanel() {
585                 return topPanel;
586         }
587
588         /**
589          * Retrieve the clipboard.
590          *
591          * @return the clipboard
592          */
593         public Clipboard getClipboard() {
594                 return clipboard;
595         }
596
597         public StatusPanel getStatusPanel() {
598                 return statusPanel;
599         }
600
601         /**
602          * Retrieve the userDetailsPanel.
603          *
604          * @return the userDetailsPanel
605          */
606         public UserDetailsPanel getUserDetailsPanel() {
607                 return userDetailsPanel;
608         }
609
610         
611
612         public String getToken() {
613                 return token;
614         }
615
616         public String getWebDAVPassword() {
617                 return webDAVPassword;
618         }
619
620         /**
621          * Retrieve the currentUserResource.
622          *
623          * @return the currentUserResource
624          */
625         public UserResource getCurrentUserResource() {
626                 return currentUserResource;
627         }
628
629         /**
630          * Modify the currentUserResource.
631          *
632          * @param newUser the new currentUserResource
633          */
634         public void setCurrentUserResource(UserResource newUser) {
635                 currentUserResource = newUser;
636         }
637
638         public static native void preventIESelection() /*-{
639                 $doc.body.onselectstart = function () { return false; };
640         }-*/;
641
642         public static native void enableIESelection() /*-{
643                 if ($doc.body.onselectstart != null)
644                 $doc.body.onselectstart = null;
645         }-*/;
646
647         /**
648          * @return the absolute path of the API root URL
649          */
650         public String getApiPath() {
651                 Configuration conf = (Configuration) GWT.create(Configuration.class);
652                 return conf.apiPath();
653         }
654
655         /**
656          * Convert server date to local time according to browser timezone
657          * and format it according to localized pattern.
658          * Time is always formatted to 24hr format.
659          * NB: This assumes that server runs in UTC timezone. Otherwise
660          * we would need to adjust for server time offset as well.
661          *
662          * @param date
663          * @return String
664          */
665         public static String formatLocalDateTime(Date date) {
666                 Date convertedDate = new Date(date.getTime() - date.getTimezoneOffset());
667                 final DateTimeFormat dateFormatter = DateTimeFormat.getShortDateFormat();
668                 final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm");
669                 String datePart = dateFormatter.format(convertedDate);
670                 String timePart = timeFormatter.format(convertedDate);
671                 return datePart + " " + timePart;
672         }
673         
674         /**
675          * History support for folder navigation
676          * adds a new browser history entry
677          *
678          * @param key
679          */
680         public void updateHistory(String key){
681 //              Replace any whitespace of the initial string to "+"
682 //              String result = key.replaceAll("\\s","+");
683 //              Add a new browser history entry.
684 //              History.newItem(result);
685                 History.newItem(key);
686         }
687
688         /**
689          * This method examines the token input and add a "/" at the end in case it's omitted.
690          * This happens only in Files/trash/, Files/shared/, Files/others.
691          *
692          * @param tokenInput
693          * @return the formated token with a "/" at the end or the same tokenInput parameter
694          */
695
696         private String handleSpecialFolderNames(String tokenInput){
697                 List<String> pathsToCheck = Arrays.asList("Files/trash", "Files/shared", "Files/others");
698                 if(pathsToCheck.contains(tokenInput))
699                         return tokenInput + "/";
700                 return tokenInput;
701
702         }
703
704         /**
705          * Reject illegal resource names, like '.' or '..' or slashes '/'.
706          */
707         static boolean isValidResourceName(String name) {
708                 if (".".equals(name) || "..".equals(name) || name.contains("/"))
709                         return false;
710                 return true;
711         }
712
713         public void putUserToMap(String _userName, String _userFullName){
714                 userFullNameMap.put(_userName, _userFullName);
715         }
716
717         public String findUserFullName(String _userName){
718                 return userFullNameMap.get(_userName);
719         }
720         public String getUserFullName(String _userName) {
721                 
722         if (GSS.get().findUserFullName(_userName) == null)
723                 //if there is no userFullName found then the map fills with the given _userName,
724                 //so userFullName = _userName
725                 GSS.get().putUserToMap(_userName, _userName);
726         else if(GSS.get().findUserFullName(_userName).indexOf('@') != -1){
727                 //if the userFullName = _userName the GetUserCommand updates the userFullName in the map
728                 GetUserCommand guc = new GetUserCommand(_userName);
729                 guc.execute();
730         }
731         return GSS.get().findUserFullName(_userName);
732         }
733         /**
734          * Retrieve the treeView.
735          *
736          * @return the treeView
737          */
738         public CellTreeView getTreeView() {
739                 return treeView;
740         }
741         
742         public void onResourceUpdate(RestResource resource,boolean clearSelection){
743                 if(resource instanceof RestResourceWrapper || resource instanceof OtherUserResource || resource instanceof TrashResource || resource instanceof SharedResource){
744                         if(getTreeView().getSelection()!=null&&getTreeView().getSelection().getUri().equals(resource.getUri()))
745                                 showFileList(resource,clearSelection);
746                 }
747                 
748         }
749 }