Aleksander Machniak
2014-05-15 47a7833aca96a263068eed53f9471a3699ef257b
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 $searchset;
128     private $result;
129     private $pagesize = 100;
130
131     public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false)
132     {
47a783 133         $this->folder     = $folder;
AM 134         $this->search     = $str;
135         $this->charset    = $charset;
566747 136         $this->sort_field = $sort_field;
47a783 137         $this->threading  = $threading;
31aa08 138
TB 139         $this->result = new rcube_result_index($folder);
140         $this->result->incomplete = true;
566747 141     }
T 142
143     public function run()
144     {
145         $this->result = $this->search_index();
146     }
147
148     /**
149      * Copy of rcube_imap::search_index()
150      */
151     protected function search_index()
152     {
153         $criteria = $this->search;
47a783 154         $charset  = $this->charset;
AM 155         $imap     = $this->worker->get_imap();
566747 156
T 157         if (!$imap->connected()) {
d93ce5 158             trigger_error("No IMAP connection for $this->folder", E_USER_WARNING);
TB 159
566747 160             if ($this->threading) {
31aa08 161                 return new rcube_result_thread($this->folder);
566747 162             }
T 163             else {
31aa08 164                 return new rcube_result_index($this->folder);
566747 165             }
T 166         }
167
168         if ($this->worker->options['skip_deleted'] && !preg_match('/UNDELETED/', $criteria)) {
169             $criteria = 'UNDELETED '.$criteria;
170         }
171
172         // unset CHARSET if criteria string is ASCII, this way
173         // SEARCH won't be re-sent after "unsupported charset" response
174         if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) {
175             $charset = 'US-ASCII';
176         }
177
178         if ($this->threading) {
179             $threads = $imap->thread($this->folder, $this->threading, $criteria, true, $charset);
180
181             // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
182             // but I've seen that Courier doesn't support UTF-8)
183             if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
184                 $threads = $imap->thread($this->folder, $this->threading,
185                     rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
186             }
b6e24c 187
566747 188             return $threads;
T 189         }
190
191         if ($this->sort_field) {
192             $messages = $imap->sort($this->folder, $this->sort_field, $criteria, true, $charset);
193
194             // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
195             // but I've seen Courier with disabled UTF-8 support)
196             if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
197                 $messages = $imap->sort($this->folder, $this->sort_field,
198                     rcube_imap::convert_criteria($criteria, $charset), true, 'US-ASCII');
199             }
b6e24c 200         }
566747 201
d53b60 202         if (!$messages || $messages->is_error()) {
b6e24c 203             $messages = $imap->search($this->folder,
TB 204                 ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true);
205
206             // Error, try with US-ASCII (some servers may support only US-ASCII)
207             if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
208                 $messages = $imap->search($this->folder,
209                     rcube_imap::convert_criteria($criteria, $charset), true);
566747 210             }
T 211         }
212
213         return $messages;
214     }
215
216     public function get_search_set()
217     {
218         return array(
219             $this->search,
220             $this->result,
221             $this->charset,
222             $this->sort_field,
223             $this->threading,
224         );
225     }
226
227     public function get_result()
228     {
229         return $this->result;
230     }
231 }