Merge branch 'hotfix-0.14.2'
[pithos-web-client] / src / gr / grnet / pithos / web / client / Pithos.java
index 7003da4..2fdf26a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
+ * Copyright 2011-2013 GRNET S.A. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or
  * without modification, are permitted provided that the following
@@ -44,13 +44,11 @@ import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.logical.shared.ResizeEvent;
 import com.google.gwt.event.logical.shared.ResizeHandler;
-import com.google.gwt.http.client.Request;
 import com.google.gwt.http.client.Response;
 import com.google.gwt.http.client.URL;
 import com.google.gwt.i18n.client.DateTimeFormat;
 import com.google.gwt.i18n.client.Dictionary;
 import com.google.gwt.i18n.client.TimeZone;
-import com.google.gwt.json.client.JSONObject;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.resources.client.ImageResource;
@@ -60,7 +58,7 @@ import com.google.gwt.user.client.ui.*;
 import com.google.gwt.view.client.SelectionChangeEvent;
 import com.google.gwt.view.client.SelectionChangeEvent.Handler;
 import com.google.gwt.view.client.SingleSelectionModel;
-import gr.grnet.pithos.web.client.catalog.GetUserCatalogs;
+import gr.grnet.pithos.web.client.catalog.UpdateUserCatalogs;
 import gr.grnet.pithos.web.client.catalog.UserCatalogs;
 import gr.grnet.pithos.web.client.commands.UploadFileCommand;
 import gr.grnet.pithos.web.client.foldertree.*;
@@ -80,12 +78,107 @@ import java.util.*;
  * Entry point classes define <code>onModuleLoad()</code>.
  */
 public class Pithos implements EntryPoint, ResizeHandler {
+    private static final boolean IsLOGEnabled = false;
+    public static final boolean IsDetailedHTTPLOGEnabled = true;
+    public static final boolean IsFullResponseBodyLOGEnabled = true;
+    private static final boolean EnableScheduledRefresh = true; // Make false only for debugging purposes.
 
-    public static final String HOME_CONTAINER = "pithos";
-
-    public static final String TRASH_CONTAINER = "trash";
+    public static final Set<String> HTTPHeadersToIgnoreInLOG = new HashSet<String>();
+    static {
+        HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_CONNECTION);
+        HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_DATE);
+        HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_KEEP_ALIVE);
+        HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_SERVER);
+        HTTPHeadersToIgnoreInLOG.add(Const.HTTP_HEADER_VARY);
+        HTTPHeadersToIgnoreInLOG.add(Const.IF_MODIFIED_SINCE);
+    }
 
     public static final Configuration config = GWT.create(Configuration.class);
+    public static final String CONFIG_API_PATH = config.apiPath();
+    static {
+        LOG("CONFIG_API_PATH = ", CONFIG_API_PATH);
+    }
+
+    public static final Dictionary otherProperties = Dictionary.getDictionary(Const.OTHER_PROPERTIES);
+    public static String getFromOtherPropertiesOrDefault(String key, String def) {
+        try {
+            final String value = otherProperties.get(key);
+            return value == null ? def : value;
+        }
+        catch(Exception e) {
+            return def;
+        }
+    }
+
+    public static String getFromOtherPropertiesOrNull(String key) {
+        return getFromOtherPropertiesOrDefault(key, null);
+    }
+
+    private static final boolean SHOW_COPYRIGHT;
+    static {
+        final String valueStr = getFromOtherPropertiesOrDefault("SHOW_COPYRIGHT", "true").trim().toLowerCase();
+        SHOW_COPYRIGHT = "true".equals(valueStr);
+        LOG("SHOW_COPYRIGHT = '", valueStr, "' ==> ", SHOW_COPYRIGHT);
+    }
+
+    public static final String OTHERPROPS_STORAGE_API_URL = getFromOtherPropertiesOrNull("STORAGE_API_URL");
+    public static final String OTHERPROPS_USER_CATALOGS_API_URL = getFromOtherPropertiesOrNull("USER_CATALOGS_API_URL");
+    static {
+        LOG("STORAGE_API_URL = ", OTHERPROPS_STORAGE_API_URL);
+        LOG("USER_CATALOGS_API_URL = ", OTHERPROPS_USER_CATALOGS_API_URL);
+    }
+
+    public static final String STORAGE_API_URL;
+    static {
+        if(OTHERPROPS_STORAGE_API_URL != null) {
+            STORAGE_API_URL = OTHERPROPS_STORAGE_API_URL;
+        }
+        else if(CONFIG_API_PATH != null) {
+            STORAGE_API_URL = CONFIG_API_PATH;
+        }
+        else {
+            throw new RuntimeException("Unknown STORAGE_API_URL");
+        }
+
+        LOG("Computed STORAGE_API_URL = ", STORAGE_API_URL);
+    }
+
+    public static final String STORAGE_VIEW_URL;
+    static {
+        final String viewURL = getFromOtherPropertiesOrNull("STORAGE_VIEW_URL");
+        if(viewURL != null) {
+            STORAGE_VIEW_URL = viewURL;
+        }
+        else {
+            STORAGE_VIEW_URL = STORAGE_API_URL;
+        }
+
+        LOG("Computed STORAGE_VIEW_URL = ", STORAGE_VIEW_URL);
+    }
+
+    public static final String PUBLIC_LINK_VIEW_PREFIX = getFromOtherPropertiesOrDefault("PUBLIC_LINK_VIEW_PREFIX", "");
+
+    public static final String USER_CATALOGS_API_URL;
+    static {
+        if(OTHERPROPS_USER_CATALOGS_API_URL != null) {
+            USER_CATALOGS_API_URL = OTHERPROPS_USER_CATALOGS_API_URL;
+        }
+        else if(OTHERPROPS_STORAGE_API_URL != null) {
+            throw new RuntimeException("STORAGE_API_URL is defined but USER_CATALOGS_API_URL is not");
+        }
+        else {
+            // https://server.com/v1/ --> https://server.com
+            String url = CONFIG_API_PATH;
+            url = Helpers.stripTrailing(url, "/");
+            url = Helpers.upToIncludingLastPart(url, "/");
+            url = Helpers.stripTrailing(url, "/");
+            url = url + "/user_catalogs";
+
+            USER_CATALOGS_API_URL = url;
+
+            LOG("Computed USER_CATALOGS_API_URL = ", USER_CATALOGS_API_URL);
+        }
+    }
 
     public interface Style extends CssResource {
         String commandAnchor();
@@ -126,6 +219,57 @@ public class Pithos implements EntryPoint, ResizeHandler {
         return userID;
     }
 
+    public UserCatalogs getUserCatalogs() {
+        return userCatalogs;
+    }
+
+    public String getCurrentUserDisplayNameOrID() {
+        final String displayName = userCatalogs.getDisplayName(getUserID());
+        return displayName == null ? getUserID() : displayName;
+    }
+
+    public boolean hasDisplayNameForUserID(String userID) {
+        return userCatalogs.getDisplayName(userID) != null;
+    }
+
+    public boolean hasIDForUserDisplayName(String userDisplayName) {
+        return userCatalogs.getID(userDisplayName) != null;
+    }
+
+    public String getDisplayNameForUserID(String userID) {
+        return userCatalogs.getDisplayName(userID);
+    }
+
+    public String getIDForUserDisplayName(String userDisplayName) {
+        return userCatalogs.getID(userDisplayName);
+    }
+
+    public List<String> getDisplayNamesForUserIDs(List<String> userIDs) {
+        if(userIDs == null) {
+            userIDs = new ArrayList<String>();
+        }
+        final List<String> userDisplayNames = new ArrayList<String>();
+        for(String userID : userIDs) {
+            final String displayName = getDisplayNameForUserID(userID);
+            userDisplayNames.add(displayName);
+        }
+
+        return userDisplayNames;
+    }
+
+    public List<String> filterUserIDsWithUnknownDisplayName(Collection<String> userIDs) {
+        if(userIDs == null) {
+            userIDs = new ArrayList<String>();
+        }
+        final List<String> filtered = new ArrayList<String>();
+        for(String userID : userIDs) {
+            if(!this.userCatalogs.hasID(userID)) {
+                filtered.add(userID);
+            }
+        }
+        return filtered;
+    }
+
     public void setAccount(AccountResource acct) {
         account = acct;
     }
@@ -229,7 +373,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
     private String userID = null;
 
     /**
-     * Hold mappings from user UUIDs to emails and vice-versa.
+     * Holds mappings from user UUIDs to emails and vice-versa.
      */
     private UserCatalogs userCatalogs = new UserCatalogs();
 
@@ -284,22 +428,87 @@ public class Pithos implements EntryPoint, ResizeHandler {
         }
     }
 
+    static native void __ConsoleLog(String message) /*-{
+      try { console.log(message); } catch (e) {}
+    }-*/;
+
+    public static void LOGError(Throwable error, StringBuilder sb) {
+        if(!isLOGEnabled()) { return; }
+
+        sb.append("\nException: [" + error.toString().replace("\n", "\n  ") + "]");
+        Throwable cause = error.getCause();
+        if(cause != null) {
+            sb.append("\nCauses:\n");
+            while(cause != null) {
+                sb.append("  ");
+                sb.append("[" + cause.toString().replace("\n", "\n  ")  + "]");
+                sb.append("\n");
+                cause = cause.getCause();
+            }
+        }
+        else {
+            sb.append("\n");
+        }
+
+        StackTraceElement[] stackTrace = error.getStackTrace();
+        sb.append("Stack trace (" + stackTrace.length + " elements):\n");
+        for(int i = 0; i < stackTrace.length; i++) {
+            StackTraceElement errorElem = stackTrace[i];
+            sb.append("  [" + i + "] ");
+            sb.append(errorElem.toString());
+            sb.append("\n");
+        }
+    }
+
+    public static void LOGError(Throwable error) {
+        if(!isLOGEnabled()) { return; }
+
+        final StringBuilder sb = new StringBuilder();
+        LOGError(error, sb);
+        if(sb.length() > 0) {
+            __ConsoleLog(sb.toString());
+        }
+    }
+
+    public static boolean isLOGEnabled() {
+        return IsLOGEnabled;
+    }
+
+    public static void LOG(Object ...args) {
+        if(!isLOGEnabled()) { return; }
+
+        final StringBuilder sb = new StringBuilder();
+        for(Object arg : args) {
+            if(arg instanceof Throwable) {
+                LOGError((Throwable) arg, sb);
+            }
+            else {
+                sb.append(arg);
+            }
+        }
+
+        if(sb.length() > 0) {
+            __ConsoleLog(sb.toString());
+        }
+    }
+
     private void initialize() {
-        System.out.println("initialize(), userToken = " + userToken);
+        userCatalogs.updateWithIDAndName("*", "All Pithos users");
+
         lastModified = new Date(); //Initialize if-modified-since value with now.
         resources.pithosCss().ensureInjected();
         boolean bareContent = Window.Location.getParameter("noframe") != null;
-        String contentWidth = bareContent ? "100%" : "75%";
+        String contentWidth = bareContent ? Const.PERCENT_100 : Const.PERCENT_75;
 
         VerticalPanel outer = new VerticalPanel();
-        outer.setWidth("100%");
+        outer.setWidth(Const.PERCENT_100);
         if(!bareContent) {
             outer.addStyleName("pithos-outer");
         }
 
         if(!bareContent) {
             topPanel = new TopPanel(this, Pithos.images);
-            topPanel.setWidth("100%");
+            topPanel.setWidth(Const.PERCENT_100);
             outer.add(topPanel);
             outer.setCellHorizontalAlignment(topPanel, HasHorizontalAlignment.ALIGN_CENTER);
         }
@@ -349,7 +558,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
         outer.setCellHorizontalAlignment(header, HasHorizontalAlignment.ALIGN_CENTER);
         // Inner contains the various lists
         inner.sinkEvents(Event.ONCONTEXTMENU);
-        inner.setWidth("100%");
+        inner.setWidth(Const.PERCENT_100);
 
         folderTreeSelectionModel = new SingleSelectionModel<Folder>();
         folderTreeSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
@@ -388,7 +597,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
         inner.add(fileList);
 
         trees = new VerticalPanel();
-        trees.setWidth("100%");
+        trees.setWidth(Const.PERCENT_100);
 
         // Add the left and right panels to the split panel.
         splitPanel.setLeftWidget(trees);
@@ -397,7 +606,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
         right.add(inner);
         splitPanel.setRightWidget(right);
         splitPanel.setSplitPosition("219px");
-        splitPanel.setSize("100%", "100%");
+        splitPanel.setSize(Const.PERCENT_100, Const.PERCENT_100);
         splitPanel.addStyleName("pithos-splitPanel");
         splitPanel.setWidth(contentWidth);
         outer.add(splitPanel);
@@ -405,7 +614,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
         if(!bareContent) {
             statusPanel = new StatusPanel();
-            statusPanel.setWidth("100%");
+            statusPanel.setWidth(Const.PERCENT_100);
             outer.add(statusPanel);
             outer.setCellHorizontalAlignment(statusPanel, HasHorizontalAlignment.ALIGN_CENTER);
         }
@@ -439,6 +648,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
             @Override
             public void execute() {
+                LOG("Pithos::initialize() Calling Pithos::fetchAccount()");
                 fetchAccount(new Command() {
 
                     @Override
@@ -451,7 +661,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                         }
                         else {
                             for(Folder f : account.getContainers()) {
-                                if(f.getName().equals(Pithos.TRASH_CONTAINER)) {
+                                if(f.getName().equals(Const.TRASH_CONTAINER)) {
                                     trash = f;
                                     break;
                                 }
@@ -482,7 +692,9 @@ public class Pithos implements EntryPoint, ResizeHandler {
         });
     }
 
-    public void scheduleResfresh() {
+    public void scheduleRefresh() {
+        if(!Pithos.EnableScheduledRefresh) { return; }
+
         Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
 
             @Override
@@ -492,7 +704,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                     return true;
                 }
 
-                HeadRequest<Folder> head = new HeadRequest<Folder>(Folder.class, getApiPath(), f.getOwner(), "/" + f.getContainer()) {
+                HeadRequest<Folder> head = new HeadRequest<Folder>(Folder.class, getStorageAPIURL(), f.getOwnerID(), "/" + f.getContainer()) {
 
                     @Override
                     public void onSuccess(Folder _result) {
@@ -502,7 +714,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
                                 @Override
                                 public void execute() {
-                                    scheduleResfresh();
+                                    scheduleRefresh();
                                 }
 
                             }, false);
@@ -512,22 +724,22 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
                                 @Override
                                 public void execute() {
-                                    scheduleResfresh();
+                                    scheduleRefresh();
                                 }
                             });
                         }
                         else {
-                            scheduleResfresh();
+                            scheduleRefresh();
                         }
                     }
 
                     @Override
                     public void onError(Throwable t) {
                         if(t instanceof RestException && ((RestException) t).getHttpStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
-                            scheduleResfresh();
+                            scheduleRefresh();
                         }
                         else if(retries >= MAX_RETRIES) {
-                            GWT.log("Error heading folder", t);
+                            LOG("Error heading folder. ", t);
                             setError(t);
                             if(t instanceof RestException) {
                                 displayError("Error heading folder: " + ((RestException) t).getHttpStatusText());
@@ -537,7 +749,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                             }
                         }
                         else {//retry
-                            GWT.log("Retry " + retries);
+                            LOG("Retry ", retries);
                             Scheduler.get().scheduleDeferred(this);
                         }
                     }
@@ -553,8 +765,8 @@ public class Pithos implements EntryPoint, ResizeHandler {
                         }
                     }
                 };
-                head.setHeader("X-Auth-Token", getUserToken());
-                head.setHeader("If-Modified-Since", DateTimeFormat.getFormat("EEE, dd MMM yyyy HH:mm:ss").format(lastModified, TimeZone.createTimeZone(0)) + " GMT");
+                head.setHeader(Const.X_AUTH_TOKEN, getUserToken());
+                head.setHeader(Const.IF_MODIFIED_SINCE, DateTimeFormat.getFormat(Const.DATE_FORMAT_1).format(lastModified, TimeZone.createTimeZone(0)) + " GMT");
                 Scheduler.get().scheduleDeferred(head);
 
                 return false;
@@ -570,7 +782,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
             }
             else {
                 Boolean[] perms = f.getPermissions().get(userID);
-                if(f.getOwner().equals(userID) || (perms != null && perms[1] != null && perms[1])) {
+                if(f.getOwnerID().equals(userID) || (perms != null && perms[1] != null && perms[1])) {
                     upload.setEnabled(true);
                     enableUploadArea();
                 }
@@ -610,18 +822,8 @@ public class Pithos implements EntryPoint, ResizeHandler {
      * Parse and store the user credentials to the appropriate fields.
      */
     private boolean parseUserCredentials() {
-        Configuration conf = (Configuration) GWT.create(Configuration.class);
-        Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
-        System.out.println("otherProperties = " + otherProperties);
-        String cookie = otherProperties.get("authCookie");
-        System.out.println("cookie = " + cookie);
-        Cookies.setCookie(cookie, "868fbb51-94a4-477d-8da3-dadad080c861|HlPjywM5FGAncsoNDzPZ2Q==");
-//        Cookies.setCookie(cookie, "868fbb51-94a4-477d-8da3-dadad080c861%7CHlPjywM5FGAncsoNDzPZ2Q%3D%3D");
-        for(String name: Cookies.getCookieNames()) {
-            System.out.println("cookie name: " + name);
-        }
+        final String cookie = otherProperties.get(Const.AUTH_COOKIE);
         String auth = Cookies.getCookie(cookie);
-        System.out.println("auth = " + auth);
         if(auth == null) {
             authenticateUser();
             return false;
@@ -632,25 +834,14 @@ public class Pithos implements EntryPoint, ResizeHandler {
         if(auth.endsWith("\"")) {
             auth = auth.substring(0, auth.length() - 1);
         }
-        String[] authSplit = auth.split("\\" + conf.cookieSeparator(), 2);
-        for(String authPart: authSplit) {
-            System.out.println("authPart: " + authPart);
-        }
+        String[] authSplit = auth.split("\\" + config.cookieSeparator(), 2);
         if(authSplit.length != 2) {
             authenticateUser();
             return false;
         }
-        userID = authSplit[0];
-        userToken = authSplit[1];
-        System.out.println("userID = " + userID);
-        System.out.println("userToken = " + userToken);
-
-        String gotoUrl = Window.Location.getParameter("goto");
-        if(gotoUrl != null && gotoUrl.length() > 0) {
-            Window.Location.assign(gotoUrl);
-            return false;
-        }
-        System.out.println("Returning true");
+        this.userID = authSplit[0];
+        this.userToken = authSplit[1];
+
         return true;
     }
 
@@ -658,59 +849,57 @@ public class Pithos implements EntryPoint, ResizeHandler {
      * Redirect the user to the login page for authentication.
      */
     protected void authenticateUser() {
-        Dictionary otherProperties = Dictionary.getDictionary("otherProperties");
-        Window.Location.assign(otherProperties.get("loginUrl") + Window.Location.getHref());
+        Dictionary otherProperties = Dictionary.getDictionary(Const.OTHER_PROPERTIES);
+        Window.Location.assign(otherProperties.get(Const.LOGIN_URL) + Window.Location.getHref());
     }
 
     public void fetchAccount(final Command callback) {
-        System.out.println("fetchAccount(), userID = " + this.userID + ", userToken = " + this.userToken);
         String path = "?format=json";
 
-        GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getApiPath(), userID, path) {
+        GetRequest<AccountResource> getAccount = new GetRequest<AccountResource>(AccountResource.class, getStorageAPIURL(), userID, path) {
             @Override
-            public void onSuccess(AccountResource _result) {
-                System.out.println("fetchAccount(), userID = " + userID + ", userToken = " + userToken + " onSuccess()");
-                account = _result;
+            public void onSuccess(AccountResource accountResource) {
+                account = accountResource;
                 if(callback != null) {
                     callback.execute();
                 }
+
+                final List<String> memberIDs = new ArrayList<String>();
+                final List<Group> groups = account.getGroups();
+                for(Group group : groups) {
+                    memberIDs.addAll(group.getMemberIDs());
+                }
+                memberIDs.add(Pithos.this.getUserID());
+
+                final List<String> theUnknown = Pithos.this.filterUserIDsWithUnknownDisplayName(memberIDs);
                 // Initialize the user catalog
-                new GetUserCatalogs(Pithos.this, Pithos.this.getUserID()) {
-                    @Override
-                    public void onSuccess(Request request, Response response, JSONObject result, UserCatalogs usersCatalog) {
-                        super.onSuccess(request, response, result, usersCatalog);
-                        Pithos.this.userCatalogs.updateFrom(usersCatalog);
-                    }
-                }.scheduleDeferred();
+                new UpdateUserCatalogs(Pithos.this, theUnknown).scheduleDeferred();
+                LOG("Called new UpdateUserCatalogs(Pithos.this, theUnknown).scheduleDeferred();");
             }
 
             @Override
             public void onError(Throwable t) {
-                System.out.println("fetchAccount(), userID = " + userID + ", userToken = " + userToken + " onError() " + t.getClass().getName() + ": " + t.getMessage());
-                GWT.log("Error getting account", t);
+                LOG("Error getting account", t);
                 setError(t);
                 if(t instanceof RestException) {
-                    System.out.println("fetchAccount(), userID = " + userID + ", userToken = " + userToken + " Error getting account: " + ((RestException) t).getHttpStatusText());
                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
                 }
                 else {
-                    System.out.println("fetchAccount(), userID = " + userID + ", userToken = " + userToken + "System error fetching user data: " + t.getMessage());
                     displayError("System error fetching user data: " + t.getMessage());
                 }
             }
 
             @Override
             protected void onUnauthorized(Response response) {
-                System.out.println("fetchAccount(), userID = " + userID + ", userToken = " + userToken + " onUnauthorized()");
                 sessionExpired();
             }
         };
-        getAccount.setHeader("X-Auth-Token", userToken);
+        getAccount.setHeader(Const.X_AUTH_TOKEN, userToken);
         Scheduler.get().scheduleDeferred(getAccount);
     }
 
     public void updateStatistics() {
-        HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getApiPath(), userID, "", account) {
+        HeadRequest<AccountResource> headAccount = new HeadRequest<AccountResource>(AccountResource.class, getStorageAPIURL(), userID, "", account) {
 
             @Override
             public void onSuccess(AccountResource _result) {
@@ -719,7 +908,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
             @Override
             public void onError(Throwable t) {
-                GWT.log("Error getting account", t);
+                LOG("Error getting account", t);
                 setError(t);
                 if(t instanceof RestException) {
                     displayError("Error getting account: " + ((RestException) t).getHttpStatusText());
@@ -734,13 +923,13 @@ public class Pithos implements EntryPoint, ResizeHandler {
                 sessionExpired();
             }
         };
-        headAccount.setHeader("X-Auth-Token", userToken);
+        headAccount.setHeader(Const.X_AUTH_TOKEN, userToken);
         Scheduler.get().scheduleDeferred(headAccount);
     }
 
     protected void createHomeContainer(final AccountResource _account, final Command callback) {
-        String path = "/" + Pithos.HOME_CONTAINER;
-        PutRequest createPithos = new PutRequest(getApiPath(), getUserID(), path) {
+        String path = "/" + Const.HOME_CONTAINER;
+        PutRequest createPithos = new PutRequest(getStorageAPIURL(), getUserID(), path) {
             @Override
             public void onSuccess(Resource result) {
                 if(!_account.hasTrashContainer()) {
@@ -753,7 +942,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
             @Override
             public void onError(Throwable t) {
-                GWT.log("Error creating pithos", t);
+                LOG("Error creating pithos", t);
                 setError(t);
                 if(t instanceof RestException) {
                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
@@ -768,13 +957,13 @@ public class Pithos implements EntryPoint, ResizeHandler {
                 sessionExpired();
             }
         };
-        createPithos.setHeader("X-Auth-Token", getUserToken());
+        createPithos.setHeader(Const.X_AUTH_TOKEN, getUserToken());
         Scheduler.get().scheduleDeferred(createPithos);
     }
 
     protected void createTrashContainer(final Command callback) {
-        String path = "/" + Pithos.TRASH_CONTAINER;
-        PutRequest createPithos = new PutRequest(getApiPath(), getUserID(), path) {
+        String path = "/" + Const.TRASH_CONTAINER;
+        PutRequest createPithos = new PutRequest(getStorageAPIURL(), getUserID(), path) {
             @Override
             public void onSuccess(Resource result) {
                 fetchAccount(callback);
@@ -782,7 +971,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
             @Override
             public void onError(Throwable t) {
-                GWT.log("Error creating pithos", t);
+                LOG("Error creating pithos", t);
                 setError(t);
                 if(t instanceof RestException) {
                     displayError("Error creating pithos: " + ((RestException) t).getHttpStatusText());
@@ -797,7 +986,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                 sessionExpired();
             }
         };
-        createPithos.setHeader("X-Auth-Token", getUserToken());
+        createPithos.setHeader(Const.X_AUTH_TOKEN, getUserToken());
         Scheduler.get().scheduleDeferred(createPithos);
     }
 
@@ -915,12 +1104,24 @@ public class Pithos implements EntryPoint, ResizeHandler {
         $doc.body.onselectstart = null;
     }-*/;
 
-    /**
-     * @return the absolute path of the API root URL
-     */
-    public String getApiPath() {
-        Configuration conf = (Configuration) GWT.create(Configuration.class);
-        return conf.apiPath();
+    public static String getStorageAPIURL() {
+        return STORAGE_API_URL;
+    }
+
+    public static String getStorageViewURL() {
+        return STORAGE_VIEW_URL;
+    }
+
+    public static boolean isShowCopyrightMessage() {
+        return SHOW_COPYRIGHT;
+    }
+
+    public static String getUserCatalogsURL() {
+        return USER_CATALOGS_API_URL;
+    }
+
+    public static String getFileViewURL(File file) {
+        return Pithos.getStorageViewURL() + file.getOwnerID() + file.getUri();
     }
 
     /**
@@ -941,7 +1142,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
         final PleaseWaitPopup pwp = new PleaseWaitPopup();
         pwp.center();
         String path = "/" + folder.getContainer() + "/" + folder.getPrefix() + "?delimiter=/" + "&t=" + System.currentTimeMillis();
-        DeleteRequest deleteFolder = new DeleteRequest(getApiPath(), folder.getOwner(), path) {
+        DeleteRequest deleteFolder = new DeleteRequest(getStorageAPIURL(), folder.getOwnerID(), path) {
 
             @Override
             protected void onUnauthorized(Response response) {
@@ -967,7 +1168,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
             @Override
             public void onError(Throwable t) {
-                GWT.log("", t);
+                LOG(t);
                 setError(t);
                 if(t instanceof RestException) {
                     if(((RestException) t).getHttpStatusCode() != Response.SC_NOT_FOUND) {
@@ -983,7 +1184,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                 pwp.hide();
             }
         };
-        deleteFolder.setHeader("X-Auth-Token", getUserToken());
+        deleteFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
         Scheduler.get().scheduleDeferred(deleteFolder);
     }
 
@@ -995,7 +1196,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
         if(iter.hasNext()) {
             File file = iter.next();
             String path = targetUri + "/" + file.getName();
-            PutRequest copyFile = new PutRequest(getApiPath(), targetUsername, path) {
+            PutRequest copyFile = new PutRequest(getStorageAPIURL(), targetUsername, path) {
                 @Override
                 public void onSuccess(Resource result) {
                     copyFiles(iter, targetUsername, targetUri, callback);
@@ -1003,7 +1204,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
                 @Override
                 public void onError(Throwable t) {
-                    GWT.log("", t);
+                    LOG(t);
                     setError(t);
                     if(t instanceof RestException) {
                         displayError("Unable to copy file: " + ((RestException) t).getHttpStatusText());
@@ -1018,12 +1219,12 @@ public class Pithos implements EntryPoint, ResizeHandler {
                     sessionExpired();
                 }
             };
-            copyFile.setHeader("X-Auth-Token", getUserToken());
-            copyFile.setHeader("X-Copy-From", URL.encodePathSegment(file.getUri()));
-            if(!file.getOwner().equals(targetUsername)) {
-                copyFile.setHeader("X-Source-Account", URL.encodePathSegment(file.getOwner()));
+            copyFile.setHeader(Const.X_AUTH_TOKEN, getUserToken());
+            copyFile.setHeader(Const.X_COPY_FROM, URL.encodePathSegment(file.getUri()));
+            if(!file.getOwnerID().equals(targetUsername)) {
+                copyFile.setHeader(Const.X_SOURCE_ACCOUNT, URL.encodePathSegment(file.getOwnerID()));
             }
-            copyFile.setHeader("Content-Type", file.getContentType());
+            copyFile.setHeader(Const.CONTENT_TYPE, file.getContentType());
             Scheduler.get().scheduleDeferred(copyFile);
         }
         else if(callback != null) {
@@ -1033,7 +1234,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
     public void copyFolder(final Folder f, final String targetUsername, final String targetUri, boolean move, final Command callback) {
         String path = targetUri + "?delimiter=/";
-        PutRequest copyFolder = new PutRequest(getApiPath(), targetUsername, path) {
+        PutRequest copyFolder = new PutRequest(getStorageAPIURL(), targetUsername, path) {
             @Override
             public void onSuccess(Resource result) {
                 if(callback != null) {
@@ -1043,7 +1244,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
             @Override
             public void onError(Throwable t) {
-                GWT.log("", t);
+                LOG(t);
                 setError(t);
                 if(t instanceof RestException) {
                     displayError("Unable to copy folder: " + ((RestException) t).getHttpStatusText());
@@ -1058,18 +1259,18 @@ public class Pithos implements EntryPoint, ResizeHandler {
                 sessionExpired();
             }
         };
-        copyFolder.setHeader("X-Auth-Token", getUserToken());
-        copyFolder.setHeader("Accept", "*/*");
-        copyFolder.setHeader("Content-Length", "0");
-        copyFolder.setHeader("Content-Type", "application/directory");
-        if(!f.getOwner().equals(targetUsername)) {
-            copyFolder.setHeader("X-Source-Account", f.getOwner());
+        copyFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
+        copyFolder.setHeader(Const.ACCEPT, "*/*");
+        copyFolder.setHeader(Const.CONTENT_LENGTH, "0");
+        copyFolder.setHeader(Const.CONTENT_TYPE, "application/directory");
+        if(!f.getOwnerID().equals(targetUsername)) {
+            copyFolder.setHeader(Const.X_SOURCE_ACCOUNT, f.getOwnerID());
         }
         if(move) {
-            copyFolder.setHeader("X-Move-From", URL.encodePathSegment(f.getUri()));
+            copyFolder.setHeader(Const.X_MOVE_FROM, URL.encodePathSegment(f.getUri()));
         }
         else {
-            copyFolder.setHeader("X-Copy-From", URL.encodePathSegment(f.getUri()));
+            copyFolder.setHeader(Const.X_COPY_FROM, URL.encodePathSegment(f.getUri()));
         }
         Scheduler.get().scheduleDeferred(copyFolder);
     }
@@ -1134,6 +1335,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
     }
 
     void createMySharedTree() {
+        LOG("Pithos::createMySharedTree()");
         mysharedTreeSelectionModel = new SingleSelectionModel<Folder>();
         mysharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
             @Override
@@ -1170,6 +1372,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
     }
 
     void createOtherSharedTree() {
+        LOG("Pithos::createOtherSharedTree()");
         otherSharedTreeSelectionModel = new SingleSelectionModel<Folder>();
         otherSharedTreeSelectionModel.addSelectionChangeHandler(new Handler() {
             @Override
@@ -1192,31 +1395,54 @@ public class Pithos implements EntryPoint, ResizeHandler {
         });
         selectionModels.add(otherSharedTreeSelectionModel);
         otherSharedTreeViewModel = new OtherSharedTreeViewModel(Pithos.this, otherSharedTreeSelectionModel);
-        otherSharedTreeViewModel.initialize(new Command() {
+        // #3784 We show it empty...
+        otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel, true);
+        trees.insert(otherSharedTreeView, 1);
 
+        LOG("Pithos::createOtherSharedTree(), initializing otherSharedTreeViewModel with a callback");
+        otherSharedTreeViewModel.initialize(new Command() {
             @Override
             public void execute() {
-                otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel);
+                // #3784 ... then remove the empty stuff and add a new view with the populated model
+                trees.remove(otherSharedTreeView);
+
+                otherSharedTreeView = new OtherSharedTreeView(otherSharedTreeViewModel, false);
                 trees.insert(otherSharedTreeView, 1);
                 treeViews.add(otherSharedTreeView);
-                scheduleResfresh();
+                scheduleRefresh();
             }
         });
     }
 
-    public native void log1(String message)/*-{
-      $wnd.console.log(message);
-    }-*/;
-
     public String getErrorData() {
-        if(error != null) {
-            return error.toString();
+        final StringBuilder sb = new StringBuilder();
+        final String NL = Const.NL;
+        Throwable t = this.error;
+        while(t != null) {
+            sb.append(t.toString());
+            sb.append(NL);
+            StackTraceElement[] traces = t.getStackTrace();
+            for(StackTraceElement trace : traces) {
+                sb.append("  [");
+                sb.append(trace.getClassName());
+                sb.append("::");
+                sb.append(trace.getMethodName());
+                sb.append("() at ");
+                sb.append(trace.getFileName());
+                sb.append(":");
+                sb.append(trace.getLineNumber());
+                sb.append("]");
+                sb.append(NL);
+            }
+            t = t.getCause();
         }
-        return "";
+
+        return sb.toString();
     }
 
     public void setError(Throwable t) {
         error = t;
+        LOG(t);
     }
 
     public void showRelevantToolbarButtons() {
@@ -1247,7 +1473,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
             }
         }
         else {
-            HeadRequest<Folder> headFolder = new HeadRequest<Folder>(Folder.class, getApiPath(), folder.getOwner(), folder.getUri(), folder) {
+            HeadRequest<Folder> headFolder = new HeadRequest<Folder>(Folder.class, getStorageAPIURL(), folder.getOwnerID(), folder.getUri(), folder) {
 
                 @Override
                 public void onSuccess(Folder _result) {
@@ -1261,7 +1487,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                     if(t instanceof RestException) {
                         if(((RestException) t).getHttpStatusCode() == Response.SC_NOT_FOUND) {
                             final String path = folder.getUri();
-                            PutRequest newFolder = new PutRequest(getApiPath(), folder.getOwner(), path) {
+                            PutRequest newFolder = new PutRequest(getStorageAPIURL(), folder.getOwnerID(), path) {
                                 @Override
                                 public void onSuccess(Resource _result) {
                                     scheduleFolderHeadCommand(folder, callback);
@@ -1269,7 +1495,6 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
                                 @Override
                                 public void onError(Throwable _t) {
-                                    GWT.log("", _t);
                                     setError(_t);
                                     if(_t instanceof RestException) {
                                         displayError("Unable to create folder: " + ((RestException) _t).getHttpStatusText());
@@ -1284,10 +1509,10 @@ public class Pithos implements EntryPoint, ResizeHandler {
                                     sessionExpired();
                                 }
                             };
-                            newFolder.setHeader("X-Auth-Token", getUserToken());
-                            newFolder.setHeader("Content-Type", "application/folder");
-                            newFolder.setHeader("Accept", "*/*");
-                            newFolder.setHeader("Content-Length", "0");
+                            newFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
+                            newFolder.setHeader(Const.CONTENT_TYPE, "application/folder");
+                            newFolder.setHeader(Const.ACCEPT, "*/*");
+                            newFolder.setHeader(Const.CONTENT_LENGTH, "0");
                             Scheduler.get().scheduleDeferred(newFolder);
                         }
                         else if(((RestException) t).getHttpStatusCode() == Response.SC_FORBIDDEN) {
@@ -1301,7 +1526,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                         displayError("System error heading folder: " + t.getMessage());
                     }
 
-                    GWT.log("Error heading folder", t);
+                    LOG("Error heading folder", t);
                     setError(t);
                 }
 
@@ -1310,13 +1535,13 @@ public class Pithos implements EntryPoint, ResizeHandler {
                     sessionExpired();
                 }
             };
-            headFolder.setHeader("X-Auth-Token", getUserToken());
+            headFolder.setHeader(Const.X_AUTH_TOKEN, getUserToken());
             Scheduler.get().scheduleDeferred(headFolder);
         }
     }
 
     public void scheduleFileHeadCommand(File f, final Command callback) {
-        HeadRequest<File> headFile = new HeadRequest<File>(File.class, getApiPath(), f.getOwner(), f.getUri(), f) {
+        HeadRequest<File> headFile = new HeadRequest<File>(File.class, getStorageAPIURL(), f.getOwnerID(), f.getUri(), f) {
 
             @Override
             public void onSuccess(File _result) {
@@ -1327,7 +1552,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
             @Override
             public void onError(Throwable t) {
-                GWT.log("Error heading file", t);
+                LOG("Error heading file", t);
                 setError(t);
                 if(t instanceof RestException) {
                     displayError("Error heading file: " + ((RestException) t).getHttpStatusText());
@@ -1342,7 +1567,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                 sessionExpired();
             }
         };
-        headFile.setHeader("X-Auth-Token", getUserToken());
+        headFile.setHeader(Const.X_AUTH_TOKEN, getUserToken());
         Scheduler.get().scheduleDeferred(headFile);
     }
 
@@ -1428,9 +1653,9 @@ public class Pithos implements EntryPoint, ResizeHandler {
         fileList.selectByUrl(selectedUrls);
     }
 
-    public void emptyContainer(final Folder container) {
+    public void purgeContainer(final Folder container) {
         String path = "/" + container.getName() + "?delimiter=/";
-        DeleteRequest delete = new DeleteRequest(getApiPath(), getUserID(), path) {
+        DeleteRequest delete = new DeleteRequest(getStorageAPIURL(), getUserID(), path) {
 
             @Override
             protected void onUnauthorized(Response response) {
@@ -1444,7 +1669,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
 
             @Override
             public void onError(Throwable t) {
-                GWT.log("Error deleting trash", t);
+                LOG("Error deleting trash", t);
                 setError(t);
                 if(t instanceof RestException) {
                     displayError("Error deleting trash: " + ((RestException) t).getHttpStatusText());
@@ -1454,7 +1679,7 @@ public class Pithos implements EntryPoint, ResizeHandler {
                 }
             }
         };
-        delete.setHeader("X-Auth-Token", getUserToken());
+        delete.setHeader(Const.X_AUTH_TOKEN, getUserToken());
         Scheduler.get().scheduleDeferred(delete);
     }
 }