Thomas Bruederli
2014-12-28 e8b82c2e7b0ae2e5d45ecb600813b8990568feb9
commit | author | age
566747 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | This file is part of the Roundcube Webmail client                     |
6  |                                                                       |
7  | Copyright (C) 2013, The Roundcube Dev Team                            |
31aa08 8  | Copyright (C) 2014, Kolab Systems AG                                  |
566747 9  |                                                                       |
T 10  | Licensed under the GNU General Public License version 3 or            |
11  | any later version with exceptions for skins & plugins.                |
12  | See the README file for a full license statement.                     |
13  |                                                                       |
14  | PURPOSE:                                                              |
15  |   Execute (multi-threaded) searches in multiple IMAP folders          |
16  +-----------------------------------------------------------------------+
17  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
18  +-----------------------------------------------------------------------+
19 */
20
21 /**
22  * Class to control search jobs on multiple IMAP folders.
23  *
24  * @package    Framework
25  * @subpackage Storage
26  * @author     Thomas Bruederli <roundcube@gmail.com>
27  */
28 class rcube_imap_search
29 {
30     public $options = array();
31
47a783 32     protected $jobs      = array();
31aa08 33     protected $timelimit = 0;
TB 34     protected $results;
35     protected $conn;
566747 36
T 37     /**
38      * Default constructor
39      */
40     public function __construct($options, $conn)
41     {
42         $this->options = $options;
47a783 43         $this->conn    = $conn;
566747 44     }
T 45
46     /**
47      * Invoke search request to IMAP server
48      *
49      * @param  array   $folders    List of IMAP folders to search in
50      * @param  string  $str        Search criteria
51      * @param  string  $charset    Search charset
52      * @param  string  $sort_field Header field to sort by
53      * @param  boolean $threading  True if threaded listing is active
54      */
55     public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null)
56     {
47a783 57         $start   = floor(microtime(true));
e8cb51 58         $results = new rcube_result_multifolder($folders);
TB 59
566747 60         // start a search job for every folder to search in
T 61         foreach ($folders as $folder) {
31aa08 62             // a complete result for this folder already exists
TB 63             $result = $this->results ? $this->results->get_set($folder) : false;
64             if ($result && !$result->incomplete) {
65                 $results->add($result);
566747 66             }
T 67             else {
47a783 68                 $search = is_array($str) && $str[$folder] ? $str[$folder] : $str;
AM 69                 $job = new rcube_imap_search_job($folder, $search, $charset, $sort_field, $threading);
566747 70                 $job->worker = $this;
T 71                 $this->jobs[] = $job;
72             }
73         }
74
31aa08 75         // execute jobs and gather results
566747 76         foreach ($this->jobs as $job) {
31aa08 77             // only run search if within the configured time limit
TB 78             // TODO: try to estimate the required time based on folder size and previous search performance
79             if (!$this->timelimit || floor(microtime(true)) - $start < $this->timelimit) {
80                 $job->run();
81             }
82
83             // add result (may have ->incomplete flag set)
566747 84             $results->add($job->get_result());
T 85         }
86
87         return $results;
88     }
89
90     /**
31aa08 91      * Setter for timelimt property
566747 92      */
31aa08 93     public function set_timelimit($seconds)
566747 94     {
31aa08 95         $this->timelimit = $seconds;
566747 96     }
T 97
98     /**
31aa08 99      * Setter for previous (potentially incomplete) search results
566747 100      */
31aa08 101     public function set_results($res)
566747 102     {
31aa08 103         $this->results = $res;
566747 104     }
31aa08 105
566747 106     /**
T 107      * Get connection to the IMAP server
108      * (used for single-thread mode)
109      */
110     public function get_imap()
111     {
112         return $this->conn;
113     }
114 }
115
116
117 /**
118  * Stackable item to run the search on a specific IMAP folder
119  */
31aa08 120 class rcube_imap_search_job /* extends Stackable */
566747 121 {
T 122     private $folder;
123     private $search;
124     private $charset;
125     private $sort_field;
126     private $threading;
127     private $result;
128
129     public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false)
130     {
47a783 131         $this->folder     = $folder;
AM 132         $this->search     = $str;
133         $this->charset    = $charset;
566747 134         $this->sort_field = $sort_field;
47a783 135         $this->threading  = $threading;
31aa08 136
TB 137         $this->result = new rcube_result_index($folder);
138         $this->result->incomplete = true;
566747 139     }
T 140
141     public function run()
142     {
143         $this->result = $this->search_index();
144     }
145
146     /**
147      * Copy of rcube_imap::search_index()
148      */
149     protected function search_index()
150     {
151         $criteria = $this->search;
47a783 152         $charset  = $this->charset;
AM 153         $imap     = $this->worker->get_imap();
566747 154
T 155         if (!$imap->connected()) {
d93ce5 156             trigger_error("No IMAP connection for $this->folder", E_USER_WARNING);
TB 157
566747 158             if ($this->threading) {
31aa08 159                 return new rcube_result_thread($this->folder);
566747 160             }
T 161             else {
31aa08 162                 return new rcube_result_index($this->folder);
566747 163             }
T 164         }
165
166         if ($this->worker->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) {
167             $criteria = 'UNDELETED '.$criteria;
168         }
169
170         // unset CHARSET if criteria string is ASCII, this way
171         // SEARCH won't be re-sent after "unsupported charset" response
172         if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) {
173             $charset = 'US-ASCII';
174         }
175
176         if ($this->threading) {
177             $threads = $imap->thread($this->folder, $this->threading, $criteria, true, $charset);
178
179             // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
180             // but I've seen that Courier doesn't support UTF-8)
181             if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
182                 $threads = $imap->thread($this->folder, $this->threading,
183                     rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
184             }
b6e24c 185
566747 186             return $threads;
T 187         }
188
189         if ($this->sort_field) {
190             $messages = $imap->sort($this->folder, $this->sort_field, $criteria, true, $charset);
191
192             // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
193             // but I've seen Courier with disabled UTF-8 support)
194             if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
195                 $messages = $imap->sort($this->folder, $this->sort_field,
196                     rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
197             }
b6e24c 198         }
566747 199
d53b60 200         if (!$messages || $messages->is_error()) {
b6e24c 201             $messages = $imap->search($this->folder,
TB 202                 ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true);
203
204             // Error, try with US-ASCII (some servers may support only US-ASCII)
205             if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
206                 $messages = $imap->search($this->folder,
207                     rcube_imap::convert_criteria($criteria, $charset), true);
566747 208             }
T 209         }
210
211         return $messages;
212     }
213
214     public function get_search_set()
215     {
216         return array(
217             $this->search,
218             $this->result,
219             $this->charset,
220             $this->sort_field,
221             $this->threading,
222         );
223     }
224
225     public function get_result()
226     {
227         return $this->result;
228     }
229 }