Changeset 4794


Ignore:
Timestamp:
Mar 2, 2009, 11:32:41 AM (14 years ago)
Author:
Nicklas Nordborg
Message:

References #1241: Implement "lazy loading" of the directory tree

I think it should be working now. Might need to check for additional cases were pages are linked into the middle of the tree.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/core/net/sf/basedb/util/FileUtil.java

    r4594 r4794  
    3030import net.sf.basedb.core.InvalidDataException;
    3131import net.sf.basedb.core.ItemQuery;
     32import net.sf.basedb.core.ItemResultIterator;
     33import net.sf.basedb.core.PermissionDeniedException;
    3234import net.sf.basedb.core.Type;
    3335import net.sf.basedb.core.query.Expressions;
     
    4951import java.util.Arrays;
    5052import java.util.HashMap;
     53import java.util.HashSet;
    5154import java.util.LinkedList;
    5255import java.util.List;
    5356import java.util.Map;
     57import java.util.Set;
    5458
    5559/**
     
    234238  /**
    235239    Get the complete tree of sub-directories from a given directory.
     240    <p>
     241    <b>NOTE!!! This method has very bad performance on large directory trees.
     242    Consider if it is not possible to use the {@link
     243    #loadMinimalDirectoryTree(DbControl, Directory...)}
     244    instead together with lazy loading of subdirectories.</b>
     245   
    236246    @param dc DbControl used to access the database.
    237247    @param directory The directory to start with
     
    276286    return tree;
    277287  }
     288 
     289  /**
     290    Load a minimal directory tree that includes at least all the specified directories
     291    and their parent directories all the way up to the root or as far as the logged in user
     292    has read permission.
     293    @param dc A DbControl for accessing the database
     294    @param directories An array of directories
     295    @return A <code>Map</code> which maps a directory to a
     296      list of it's sub-directories. The map is guaranteed to contain an entry for each of
     297      the given directories, but the list may be empty if there are no subdirectories
     298    @throws BaseException If there is an error
     299    @since 2.11
     300  */
     301  public static Map<Directory, List<Directory>> loadMinimalDirectoryTree(DbControl dc, Directory... directories)
     302    throws InvalidDataException, BaseException
     303  {
     304    Map<Directory, List<Directory>> tree = new HashMap<Directory, List<Directory>>();
     305
     306    // 1. Move up the directory tree as far as possible
     307    //    storing all directories that we pass
     308    Set<Integer> all = new HashSet<Integer>();
     309    for (Directory d : directories)
     310    {
     311      if (d == null) continue;
     312      tree.put(d, new LinkedList<Directory>());
     313      all.add(d.getId());
     314      try
     315      {
     316        Directory parent = d.getParent();
     317        while (parent != null)
     318        {
     319          if (all.add(parent.getId()))
     320          {
     321            parent = parent.getParent();
     322          }
     323          else
     324          {
     325            // break if the directory has already been added
     326            parent = null;
     327          }
     328        }
     329      }
     330      catch (PermissionDeniedException ex)
     331      {}
     332    }
     333
     334    // 2. Query that loads the subdirectories to all directories that we passed
     335    ItemQuery<Directory> query = Directory.getQuery();
     336    query.restrict(
     337      Restrictions.in(
     338        Hql.property("parent"),
     339        Expressions.parameter("parents")
     340      )
     341    );
     342    query.order(Orders.asc(Hql.property("name")));
     343    query.include(Include.MINE, Include.SHARED, Include.OTHERS, Include.NOT_REMOVED, Include.REMOVED);
     344    query.setParameter("parents", all, Type.INT);
     345   
     346    ItemResultIterator<Directory> it = query.iterate(dc);
     347    while (it.hasNext())
     348    {
     349      Directory child = it.next();
     350      Directory parent = child.getParent();
     351      if (!tree.containsKey(parent))
     352      {
     353        tree.put(parent, new LinkedList<Directory>());
     354      }
     355      tree.get(parent).add(child);
     356    }
     357    return tree;
     358  }
     359 
    278360 
    279361  /**
  • trunk/www/filemanager/directories/list_directories.jsp

    r4744 r4794  
    4444  import="net.sf.basedb.util.Values"
    4545  import="java.util.Map"
     46  import="java.util.Set"
     47  import="java.util.HashSet"
    4648  import="java.util.List"
    4749%>
     
    4951<%@ taglib prefix="tbl" uri="/WEB-INF/table.tld" %>
    5052<%!
    51 String generateSubTree(Map<Directory, List<Directory>> tree, Directory parent, String ID, String parentId)
     53String generateSubTree(Map<Directory, List<Directory>> tree, Set<Directory> ignore, Directory parent, String ID, String parentId)
    5254{
     55  if (ignore.contains(parent)) return "";
     56  ignore.add(parent);
    5357  StringBuilder sb = new StringBuilder();
    5458  if (parentId == null) parentId = "dir"+parent.getId();
     
    5761  for (Directory child : children)
    5862  {
     63    if (ignore.contains(child)) continue;
    5964    String folderIcon = "Folder";
    6065    if (child.isHomeDirectory())
     
    7075      folderIcon = "FolderCompressed";
    7176    }
    72     sb.append("var dir").append(child.getId()).append(" = JoustMenu.addChildItem(").append(parentId);
     77    sb.append("var dir").append(child.getId()).append(" = JoustMenu.addLazyChildItem(").append(parentId);
    7378    sb.append(",'").append(folderIcon).append("'");
    7479    sb.append(",'").append(HTML.javaScriptEncode(child.getName())).append("'");
    7580    sb.append(",'directoryOnClick(").append(child.getId()).append(",");
    7681    sb.append(" \"").append(HTML.javaScriptEncode(child.getPath().toString())).append("\")'");
    77     sb.append(",'', 'D").append(child.getId()).append("')\n");
    78     sb.append(generateSubTree(tree, child, ID, null));
     82    sb.append(",'', 'D").append(child.getId()).append("',");
     83    sb.append("'lazyInitSubdir(").append(child.getId()).append(")')\n");
     84    sb.append(generateSubTree(tree, ignore, child, ID, null));
    7985  }
    8086  return sb.toString();
     
    98104  final boolean readDirectories = sc.hasPermission(Permission.READ, Item.DIRECTORY);
    99105  final Directory root = Directory.getById(dc, SystemItems.getId(readDirectories ? Directory.ROOT : Directory.HOME));
    100 
     106  Directory current = directoryId != 0 ? Directory.getById(dc, directoryId) : null;
     107  if (current == null) current = userHome;
     108  if (current == null) current = root;
     109 
    101110  final String rootIcon;
    102111  final String rootTitle;
     
    111120    rootTitle = "Other users";
    112121  }
    113   Map<Directory, List<Directory>> tree = FileUtil.getDirectoryTree(dc, root);
     122  Map<Directory, List<Directory>> tree = FileUtil.loadMinimalDirectoryTree(dc, root, userHome, current);
     123  Set<Directory> ignore = new HashSet<Directory>();
    114124  %>
    115125  <base:page title="<%=requestTitle != null ? requestTitle : ""%>" type="popup">
    116   <base:head scripts="newjoust.js,table.js" styles="newjoust.css,toolbar.css">
     126  <base:head scripts="newjoust.js,table.js,ajax.js" styles="newjoust.css,toolbar.css">
    117127  <script language="JavaScript">
    118128  var isInitialised = false;
     
    122132    IconStore.init();
    123133    <%
     134    // Root entry for the user's Home directory
    124135    if (userHome != null)
    125136    {
     
    128139      JoustMenu.menuItems[userHome].noOutlineIcon = true;
    129140      JoustMenu.menuItems[userHome].isOpen = true;
    130       <%=generateSubTree(tree, userHome, ID, "userHome")%>
     141      <%=generateSubTree(tree, ignore, userHome, ID, "userHome")%>
    131142      <%
    132143    }
    133     %>
    134 
     144    // Root entry for entire directory tree
     145    %>
    135146    var root = JoustMenu.addMenuItem(-1, '<%=rootIcon%>', '<%=HTML.javaScriptEncode(rootTitle)%>', 'directoryOnClick(<%=root.getId()%>, "<%=HTML.javaScriptEncode(root.getPath().toString())%>")', '', 'D<%=root.getId()%>');
    136147    JoustMenu.menuItems[root].noOutlineIcon = true;
    137148    JoustMenu.menuItems[root].isOpen = true;
    138     <%=generateSubTree(tree, root, ID, "root")%>
    139    
    140     // Show directory separatly if read permission isn't granted all the way to root.
    141     if (!JoustMenu.menuItems['D<%=directoryId%>'])
    142     {
    143       <%
     149    <%=generateSubTree(tree, ignore, root, ID, "root")%>
     150
     151    <%
     152    // Add 'current' as root entry if we have not seen it before
     153    if (!ignore.contains(current))
     154    {
     155      // but... move up as far as the user has 'read' permission...
     156      Directory currentRoot = current;
    144157      try
    145158      {
    146         Directory currentDir = Directory.getById(dc, directoryId);
    147         Map<Directory,List<Directory>> subTree = FileUtil.getDirectoryTree(dc, currentDir);
    148         %>
    149         var currentDir = JoustMenu.addMenuItem(0, 'Folder', '<%=HTML.javaScriptEncode(currentDir.getName())%>', 'directoryOnClick(<%=currentDir.getId()%>, "<%=HTML.javaScriptEncode(currentDir.getPath().toString())%>")', '', 'D<%=currentDir.getId()%>');
    150         JoustMenu.menuItems[currentDir].noOutlineIcon = true;
    151         JoustMenu.menuItems[currentDir].isOpen = true;
    152         <%=generateSubTree(subTree, currentDir, ID, "currentDir")%>
    153         <%
     159        while (currentRoot != null && !ignore.contains(currentRoot.getParent()))
     160        {
     161          currentRoot = currentRoot.getParent();
     162        }
    154163      }
    155       catch (Throwable ex){}     
     164      catch (Throwable t)
     165      {}
     166      if (currentRoot == null) currentRoot = current;
    156167      %>
    157     }
     168      var currentRoot = JoustMenu.addMenuItem(-1, 'Folder', '/<i>denied</i>/<%=HTML.javaScriptEncode(currentRoot.getName())%>', 'directoryOnClick(<%=currentRoot.getId()%>, "<%=HTML.javaScriptEncode(currentRoot.getPath().toString())%>")', '', 'D<%=currentRoot.getId()%>');
     169      JoustMenu.menuItems[currentRoot].noOutlineIcon = true;
     170      JoustMenu.menuItems[currentRoot].isOpen = true;
     171      <%=generateSubTree(tree, ignore, currentRoot, ID, "currentRoot")%>
     172      <%
     173    }
     174    %>
    158175    JoustMenu.draw('joust');
    159     var lastDirectory;
    160     <%
    161     if (directoryId != 0)
    162     {
    163       %>
    164       lastDirectory = JoustMenu.menuItems['D<%=directoryId%>'];
    165       <%
    166     }
    167     %>
    168     JoustMenu.select(lastDirectory ? lastDirectory.index : <%=userHome != null ? "userHome" : "root"%>);
     176    var lastDirectory = JoustMenu.menuItems['D<%=current.getId()%>'];
     177    JoustMenu.select(lastDirectory.index);
    169178    JoustMenu.alwaysSendOnClickToSelected = true;
    170179    isInitialised = true;
    171180  }
    172181 
     182  function lazyInitSubdir(directoryId)
     183  {
     184    var request = Ajax.getXmlHttpRequest();
     185    var url = 'ajax.jsp?ID=<%=ID%>&cmd=GetSubdirectories&item_id=' + directoryId;
     186    request.open("GET", url, false);
     187    // NOTE! 'false' causes code to wait for the response. aka. 'Synchronous AJAX' or SJAX.
     188    request.send(null);
     189    var response = request.responseText.split('\n');
     190   
     191    var kv = new Array();
     192    var numAdded = 0;
     193    var parent = JoustMenu.menuItems['D'+directoryId];
     194    for (var i = 0; i < response.length; i++)
     195    {
     196      var txt = Main.trimString(response[i]);
     197      //alert(txt.length+':'+txt);
     198      if (txt == 'end')
     199      {
     200        var id = kv['id'];
     201        var folderIcon = 'Folder';
     202        if (kv['home'] == 1)
     203        {
     204          folderIcon = 'UserHome';
     205        }
     206        else if (kv['removed'] == 1)
     207        {
     208          folderIcon = 'FolderDeleted';
     209        }
     210        else if (kv['auto-compress'] == 1)
     211        {
     212          folderIcon = 'FolderCompressed';
     213        }
     214        numAdded++;
     215        JoustMenu.addLazyChildItem(parent.index, folderIcon, kv['name'], 'directoryOnClick('+id+',\''+kv['path']+'\')', '', 'D'+id, 'lazyInitSubdir('+id+')');
     216      }
     217      else
     218      {
     219        var tmp = txt.split(':', 2);
     220        kv[tmp[0]] = tmp[1];
     221      }
     222    }
     223    //alert(numAdded);
     224    return numAdded > 0;
     225  }
    173226  <%
    174227  if ("selectonedirectory".equals(mode))
  • trunk/www/include/scripts/newjoust.js

    r4793 r4794  
    8282      {
    8383        html += '<div class="children" id="children'+menuItem.index+'" style="display: '+(menuItem.isOpen ? 'block' : 'none')+'">';
     84        menuItem.childIndentation = indentString+padHtml;
    8485        if (menuItem.firstChildIndex != -1)
    8586        {
    86           html += this.drawMenuItems(menuItem.firstChildIndex, indentString+padHtml);
    87         }
    88         else
    89         {
    90           menuItem.childIndentation = indentString+padHtml;
     87          html += this.drawMenuItems(menuItem.firstChildIndex, menuItem.childIndentation);
    9188        }
    9289        html += '</div>\n';
     
    144141  this.addLazyChildItem = function(parentItemIndex, iconName, text, onclick, tooltip, externalId, lazyInit)
    145142  {
    146     this.addChildItem(parentItemIndex, iconName, text, onclick, tooltip, externalId, false, lazyInit);
     143    return this.addChildItem(parentItemIndex, iconName, text, onclick, tooltip, externalId, false, lazyInit);
    147144  }
    148145 
     
    366363  this.firstChildIndex = -1;
    367364  this.lastChildIndex = -1;
     365  if (parentItemIndex != -1) menu.menuItems[parentItemIndex].lazyInit = null;
    368366 
    369367  /**
Note: See TracChangeset for help on using the changeset viewer.