From 254d5ef32b7ec45a48abd43f19c84168dabe13d1 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Fri, 20 May 2011 06:38:44 -0400
Subject: [PATCH] - Improve performence of folder manager operations by moving subscriptions table operations (like adding/updateing/moving folders) into client-side - no need to invoke LIST, do sorting in browser - This change should also handle better situations when working with replicated IMAP backend (e.g.Cyrus Murder)

---
 CHANGELOG                              |    1 
 program/steps/settings/edit_folder.inc |    6 
 program/steps/settings/folders.inc     |   44 ------
 program/steps/settings/func.inc        |   23 +++
 program/steps/settings/save_folder.inc |    8 
 program/js/app.js                      |  265 ++++++++++++++++++++++++++++---------------
 6 files changed, 203 insertions(+), 144 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 53515f6..4958bf8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Improve performence of folder manager operations
 - Fix default_port option handling in Installer when config.inc.php file exists (#1487925)
 - Removed option focus_on_new_message, added newmail_notifier plugin
 - Added general rcube_cache class with Memcache and APC support
diff --git a/program/js/app.js b/program/js/app.js
index a114c80..ea54b0b 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -4016,7 +4016,6 @@
     this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid });
   };
 
-
   this.init_edit_field = function(col, elem)
   {
     if (!elem)
@@ -4179,21 +4178,6 @@
   /*********        user settings methods          *********/
   /*********************************************************/
 
-  this.init_subscription_list = function()
-  {
-    var p = this;
-    this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
-      {multiselect:false, draggable:true, keyboard:false, toggleselect:true});
-    this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
-    this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
-    this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
-    this.subscription_list.row_init = function (row) {
-      row.obj.onmouseover = function() { p.focus_subscription(row.id); };
-      row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
-    };
-    this.subscription_list.init();
-  };
-
   // preferences section select and load options frame
   this.section_select = function(list)
   {
@@ -4256,6 +4240,26 @@
     this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
 
     return true;
+  };
+
+
+  /*********************************************************/
+  /*********        folder manager methods         *********/
+  /*********************************************************/
+
+  this.init_subscription_list = function()
+  {
+    var p = this;
+    this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
+      {multiselect:false, draggable:true, keyboard:false, toggleselect:true});
+    this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
+    this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
+    this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
+    this.subscription_list.row_init = function (row) {
+      row.obj.onmouseover = function() { p.focus_subscription(row.id); };
+      row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
+    };
+    this.subscription_list.init();
   };
 
   this.focus_subscription = function(id)
@@ -4347,90 +4351,182 @@
     }
   };
 
-  // add a new folder to the subscription list by cloning a folder row
-  this.add_folder_row = function(name, display_name, replace, before)
+  // Add folder row to the table and initialize it
+  this.add_folder_row = function (name, display_name, protected, subscribed, skip_init)
   {
     if (!this.gui_objects.subscriptionlist)
       return false;
 
-    // find not protected folder
-    var refid;
-    for (var rid in this.env.subscriptionrows) {
-      if (this.env.subscriptionrows[rid]!=null && !this.env.subscriptionrows[rid][2]) {
-        refid = rid;
-        break;
-      }
-    }
-
-    var refrow, form,
+    var row, n, i, tmp, folders, len, list = [], slist = [],
       tbody = this.gui_objects.subscriptionlist.tBodies[0],
-      id = 'rcmrow'+(tbody.childNodes.length+1),
-      selection = this.subscription_list.get_single_selection();
+      refrow = $('tr', tbody).get(0),
+      id = 'rcmrow'+((new Date).getTime());
 
-    if (replace && replace.id) {
-      id = replace.id;
-      refid = replace.id;
-    }
-
-    if (!id || !refid || !(refrow = document.getElementById(refid))) {
+    if (!refrow) {
       // Refresh page if we don't have a table row to clone
       this.goto_url('folders');
       return false;
     }
 
     // clone a table row if there are existing rows
-    var row = this.clone_table_row(refrow);
-    row.id = id;
-
-    if (before && (before = this.get_folder_row_id(before)))
-      tbody.insertBefore(row, document.getElementById(before));
-    else
-      tbody.appendChild(row);
-
-    if (replace)
-      tbody.removeChild(replace);
-
-    // add to folder/row-ID map
-    this.env.subscriptionrows[row.id] = [name, display_name, 0];
+    row    = $(refrow).clone(true);
+    row.attr('id', id);
 
     // set folder name
-    row.cells[0].innerHTML = display_name;
+    row.find('td:first').html(display_name);
 
-    if (!replace) {
-      // set messages count to zero
-      row.cells[1].innerHTML = '*';
+    // update subscription checkbox
+    $('input[name="_subscribed[]"]', row).val(name)
+      .prop({checked: subscribed ? true : false, disabled: protected ? true : false});
 
-      // update subscription checkbox
-      $('input[name="_subscribed[]"]', row).val(name).prop('checked', true);
+    // add to folder/row-ID map
+    this.env.subscriptionrows[id] = [name, display_name, 0];
+
+    // sort folders, to find a place where to insert the row
+    folders = this.env.subscriptionrows;
+    for (n in folders) {
+      // protected folder
+      if (folders[n][2]) {
+        slist.push(folders[n][0]);
+        tmp = folders[n][0]+this.env.delimiter;
+      }
+      // protected folder's child
+      else if (tmp && folders[n][0].indexOf(tmp) == 0)
+        slist.push(folders[n][0]);
+      // other
+      else {
+        list.push(folders[n][0]);
+        tmp = null;
+      }
+    }
+    list.sort();
+    // make sure protected folders (and their subs) are on top
+    list = slist.concat(list);
+
+    // find folder position after sorting
+    for (n=0, len=list.length; n<len; n++) {
+      if (list[n] == name)
+        break;
     }
 
-    this.init_subscription_list();
-    if (selection && document.getElementById('rcmrow'+selection))
-      this.subscription_list.select_row(selection);
+    // add row to the table
+    if (n && n < len)
+      $('#'+this.get_folder_row_id(list[n-1])).after(row);
+    else
+      row.appendTo(tbody);
 
-    if (document.getElementById(id).scrollIntoView)
-      document.getElementById(id).scrollIntoView();
+    // update list widget
+    this.subscription_list.clear_selection();
+    if (!skip_init)
+      this.init_subscription_list();
+
+    row = row.get(0);
+    if (row.scrollIntoView)
+      row.scrollIntoView();
+
+    return row;
   };
 
-  // replace an existing table row with a new folder line
-  this.replace_folder_row = function(oldfolder, newfolder, display_name, before)
+  // replace an existing table row with a new folder line (with subfolders)
+  this.replace_folder_row = function(oldfolder, newfolder, display_name, protected)
   {
-    var id = this.get_folder_row_id(oldfolder),
-      row = document.getElementById(id);
+    if (!this.gui_objects.subscriptionlist)
+      return false;
 
-    // replace an existing table row (if found)
-    this.add_folder_row(newfolder, display_name, row, before);
+    var i, n, len, name, dispname, oldrow, tmprow, row, level,
+      tbody = this.gui_objects.subscriptionlist.tBodies[0],
+      folders = this.env.subscriptionrows,
+      id = this.get_folder_row_id(oldfolder),
+      regex = new RegExp('^'+RegExp.escape(oldfolder)),
+      subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
+      // find subfolders of renamed folder
+      list = this.get_subfolders(oldfolder);
+
+    // replace an existing table row
+    this._remove_folder_row(id);
+    row = $(this.add_folder_row(newfolder, display_name, protected, subscribed, true));
+
+    // detect tree depth change
+    if (len = list.length) {
+      level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length;
+    }
+
+    // move subfolders to the new branch
+    for (n=0; n<len; n++) {
+      id = list[n];
+      name = this.env.subscriptionrows[id][0];
+      dispname = this.env.subscriptionrows[id][1];
+      oldrow = $('#'+id);
+      tmprow = oldrow.clone(true);
+      oldrow.remove();
+      row.after(tmprow);
+      row = tmprow;
+      // update folder index
+      name = name.replace(regex, newfolder);
+      $('input[name="_subscribed[]"]', row).val(name);
+      this.env.subscriptionrows[id][0] = name;
+      // update the name if level is changed
+      if (level != 0) {
+        if (level > 0) {
+          for (i=level; i>0; i--)
+            dispname = dispname.replace(/^&nbsp;&nbsp;&nbsp;&nbsp;/, '');
+        }
+        else {
+          for (i=level; i<0; i++)
+            dispname = '&nbsp;&nbsp;&nbsp;&nbsp;' + dispname;
+        }
+        row.find('td:first').html(dispname);
+        this.env.subscriptionrows[id][1] = dispname;
+      }
+    }
+
+    // update list widget
+    this.init_subscription_list();
   };
 
   // remove the table row of a specific mailbox from the table
-  // (the row will not be removed, just hidden)
-  this.remove_folder_row = function(folder)
+  this.remove_folder_row = function(folder, subs)
   {
-    var row, id = this.get_folder_row_id(folder);
+    var n, len, list = [], id = this.get_folder_row_id(folder);
 
-    if (id && (row = document.getElementById(id)))
-      row.style.display = 'none';
+    // get subfolders if any
+    if (subs)
+      list = this.get_subfolders(folder);
+
+    // remove old row
+    this._remove_folder_row(id);
+
+    // remove subfolders
+    for (n=0, len=list.length; n<len; n++)
+      this._remove_folder_row(list[n]);
   };
+
+  this._remove_folder_row = function(id)
+  {
+    this.subscription_list.remove_row(id.replace(/^rcmrow/, ''));
+    $('#'+id).remove();
+    delete this.env.subscriptionrows[id];
+  }
+
+  this.get_subfolders = function(folder)
+  {
+    var name, list = [],
+      regex = new RegExp('^'+RegExp.escape(folder)+RegExp.escape(this.env.delimiter)),
+      row = $('#'+this.get_folder_row_id(folder)).get(0);
+
+    while (row = row.nextSibling) {
+      if (row.id) {
+        name = this.env.subscriptionrows[row.id][0];
+        if (regex.test(name)) {
+          list.push(row.id);
+        }
+        else
+          break;
+      }
+    }
+
+    return list;
+  }
 
   this.subscribe = function(folder)
   {
@@ -4451,33 +4547,12 @@
   // helper method to find a specific mailbox row ID
   this.get_folder_row_id = function(folder)
   {
-    for (var id in this.env.subscriptionrows)
-      if (this.env.subscriptionrows[id] && this.env.subscriptionrows[id][0] == folder)
+    var id, folders = this.env.subscriptionrows;
+    for (id in folders)
+      if (folders[id] && folders[id][0] == folder)
         break;
 
     return id;
-  };
-
-  // duplicate a specific table row
-  this.clone_table_row = function(row)
-  {
-    var cell, td,
-      new_row = document.createElement('tr');
-
-    for (var n=0; n<row.cells.length; n++) {
-      cell = row.cells[n];
-      td = document.createElement('td');
-
-      if (cell.className)
-        td.className = cell.className;
-      if (cell.align)
-        td.setAttribute('align', cell.align);
-
-      td.innerHTML = cell.innerHTML;
-      new_row.appendChild(td);
-    }
-
-    return new_row;
   };
 
   // when user select a folder in manager
diff --git a/program/steps/settings/edit_folder.inc b/program/steps/settings/edit_folder.inc
index bc43103..0bc7ab6 100644
--- a/program/steps/settings/edit_folder.inc
+++ b/program/steps/settings/edit_folder.inc
@@ -24,7 +24,7 @@
 // init IMAP connection
 $RCMAIL->imap_connect();
 
-function rcube_folder_form($attrib)
+function rcmail_folder_form($attrib)
 {
     global $RCMAIL;
 
@@ -41,7 +41,7 @@
 
     // Get mailbox parameters
     if (strlen($mbox)) {
-        $options   = rcube_folder_options($mbox_imap);
+        $options   = rcmail_folder_options($mbox_imap);
         $namespace = $RCMAIL->imap->get_namespace();
 
         $path   = explode($delimiter, $mbox_imap);
@@ -319,7 +319,7 @@
 
 // register UI objects
 $OUTPUT->add_handlers(array(
-    'folderdetails' => 'rcube_folder_form',
+    'folderdetails' => 'rcmail_folder_form',
 ));
 
 $OUTPUT->add_label('nonamewarning');
diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc
index bc95c75..a906809 100644
--- a/program/steps/settings/folders.inc
+++ b/program/steps/settings/folders.inc
@@ -76,24 +76,12 @@
     $mbox_utf8 = get_input_value('_mbox', RCUBE_INPUT_POST, true);
     $mbox      = rcube_charset_convert($mbox_utf8, RCMAIL_CHARSET, 'UTF7-IMAP');
 
-    // get folder's children or all folders if the name contains special characters
-    $delimiter = $IMAP->get_hierarchy_delimiter();
-    if ((strpos($mbox, '%') === false) && (strpos($mbox, '*') === false))
-        $a_mboxes  = $IMAP->list_unsubscribed('', $mbox.$delimiter.'*');
-    else
-        $a_mboxes  = $IMAP->list_unsubscribed();
-
     if (strlen($mbox))
         $deleted = $IMAP->delete_mailbox($mbox);
 
     if ($OUTPUT->ajax_call && $deleted) {
         // Remove folder and subfolders rows
-        $OUTPUT->command('remove_folder_row', $mbox_utf8);
-        foreach ($a_mboxes as $folder) {
-            if (preg_match('/^'. preg_quote($mbox.$delimiter, '/') .'/', $folder)) {
-                $OUTPUT->command('remove_folder_row', rcube_charset_convert($folder, 'UTF7-IMAP'));
-            }
-        }
+        $OUTPUT->command('remove_folder_row', $mbox_utf8, true);
         $OUTPUT->show_message('folderdeleted', 'confirmation');
         // Clear content frame
         $OUTPUT->command('subscription_select');
@@ -118,34 +106,7 @@
     }
 
     if ($rename && $OUTPUT->ajax_call) {
-        $folderlist = $IMAP->list_unsubscribed();
-        $delimiter  = $IMAP->get_hierarchy_delimiter();
-
-        $regexp = '/^' . preg_quote($name . $delimiter, '/') . '/';
-
-        // subfolders
-        for ($x=sizeof($folderlist)-1; $x>=0; $x--) {
-            if (preg_match($regexp, $folderlist[$x])) {
-                $oldfolder   = $oldname . $delimiter . preg_replace($regexp, '', $folderlist[$x]);
-                $foldersplit = explode($delimiter, $IMAP->mod_mailbox($folderlist[$x]));
-                $level       = count($foldersplit) - 1;
-                $display_rename = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $level) 
-                    . rcube_charset_convert($foldersplit[$level], 'UTF7-IMAP');
-                $before = isset($folderlist[$x+1]) ? rcube_charset_convert($folderlist[$x+1], 'UTF7-IMAP') : false;
-
-                $OUTPUT->command('replace_folder_row', rcube_charset_convert($oldfolder, 'UTF7-IMAP'),
-                    rcube_charset_convert($folderlist[$x], 'UTF7-IMAP'), $display_rename, $before);
-            }
-        }
-
-        $index       = array_search($name, $folderlist);
-        $name        = $IMAP->mod_mailbox($name);
-        $foldersplit = explode($delimiter, $name);
-        $level       = count($foldersplit) - 1;
-        $display_rename = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $level) . rcube_charset_convert($foldersplit[$level], 'UTF7-IMAP');
-        $before         = $index !== false && isset($folderlist[$index+1]) ? rcube_charset_convert($folderlist[$index+1], 'UTF7-IMAP') : false;
-
-        $OUTPUT->command('replace_folder_row', $oldname_utf8, $name_utf8, $display_rename, $before);
+        rcmail_update_folder_row($name, $oldname);
     }
     else if (!$rename) {
         rcmail_display_server_error('errorsaving');
@@ -375,6 +336,7 @@
     return false;
 }
 
+
 $OUTPUT->set_pagetitle(rcube_label('folders'));
 $OUTPUT->include_script('list.js');
 $OUTPUT->set_env('quota', $IMAP->get_capability('QUOTA'));
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index b204d9b..a44d6c8 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -747,7 +747,7 @@
 }
 
 
-function rcube_folder_options($mailbox)
+function rcmail_folder_options($mailbox)
 {
     global $RCMAIL;
 
@@ -785,6 +785,27 @@
     return $options;    
 }
 
+// Updates (or creates) folder row in the subscriptions table
+function rcmail_update_folder_row($name, $oldname=null)
+{
+    global $IMAP, $CONFIG, $OUTPUT;
+
+    $delimiter    = $IMAP->get_hierarchy_delimiter();
+    $name_utf8    = rcube_charset_convert($name, 'UTF7-IMAP');
+    $protected    = ($CONFIG['protect_default_folders'] == true && in_array($name, $CONFIG['default_imap_folders']));
+
+    $foldersplit  = explode($delimiter, $IMAP->mod_mailbox($name));
+    $level        = count($foldersplit) - 1;
+    $display_name = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $level)
+        . Q($protected ? rcmail_localize_foldername($name) : rcube_charset_convert($foldersplit[$level], 'UTF7-IMAP'));
+
+    if ($oldname === null)
+        $OUTPUT->command('add_folder_row', $name_utf8, $display_name, $protected, true);
+    else
+        $OUTPUT->command('replace_folder_row', rcube_charset_convert($oldname, 'UTF7-IMAP'),
+            $name_utf8, $display_name, $protected);
+}
+
 
 // register UI objects
 $OUTPUT->add_handlers(array(
diff --git a/program/steps/settings/save_folder.inc b/program/steps/settings/save_folder.inc
index c112096..a4e752c 100644
--- a/program/steps/settings/save_folder.inc
+++ b/program/steps/settings/save_folder.inc
@@ -34,7 +34,7 @@
 // $path is in UTF7-IMAP already
 
 $delimiter = $IMAP->get_hierarchy_delimiter();
-$options = strlen($old_imap) ? rcube_folder_options($old_imap) : array();
+$options = strlen($old_imap) ? rcmail_folder_options($old_imap) : array();
 
 // Folder name checks
 if ($options['protected'] || $options['norename']) {
@@ -105,9 +105,9 @@
 
             $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded));
         }
-
+  
+        rcmail_update_folder_row($folder['name']);
         $OUTPUT->show_message('foldercreated', 'confirmation');
-        $OUTPUT->command('reload', 250);
         $OUTPUT->send('iframe');
     }
     else {
@@ -163,7 +163,7 @@
 
         $OUTPUT->show_message('folderupdated', 'confirmation');
         if ($rename) {
-            $OUTPUT->command('reload', 250);
+            rcmail_update_folder_row($folder['name'], $folder['oldname']);
             $OUTPUT->send('iframe');
         }
     }

--
Gitblit v1.9.1