Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117751791
kolab_storage_cache.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
32 KB
Referenced Files
None
Subscribers
None
kolab_storage_cache.php
View Options
<?php
/**
* Kolab storage cache class providing a local caching layer for Kolab groupware objects.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012-2013, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class
kolab_storage_cache
{
const
DB_DATE_FORMAT
=
'Y-m-d H:i:s'
;
protected
$db
;
protected
$imap
;
protected
$folder
;
protected
$uid2msg
;
protected
$objects
;
protected
$metadata
=
array
();
protected
$folder_id
;
protected
$resource_uri
;
protected
$enabled
=
true
;
protected
$synched
=
false
;
protected
$synclock
=
false
;
protected
$ready
=
false
;
protected
$cache_table
;
protected
$folders_table
;
protected
$max_sql_packet
;
protected
$max_sync_lock_time
=
600
;
protected
$binary_items
=
array
();
protected
$extra_cols
=
array
();
protected
$order_by
=
null
;
protected
$limit
=
null
;
/**
* Factory constructor
*/
public
static
function
factory
(
kolab_storage_folder
$storage_folder
)
{
$subclass
=
'kolab_storage_cache_'
.
$storage_folder
->
type
;
if
(
class_exists
(
$subclass
))
{
return
new
$subclass
(
$storage_folder
);
}
else
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'type'
=>
'php'
,
'message'
=>
"No kolab_storage_cache class found for folder '$storage_folder->name' of type '$storage_folder->type'"
),
true
);
return
new
kolab_storage_cache
(
$storage_folder
);
}
}
/**
* Default constructor
*/
public
function
__construct
(
kolab_storage_folder
$storage_folder
=
null
)
{
$rcmail
=
rcube
::
get_instance
();
$this
->
db
=
$rcmail
->
get_dbh
();
$this
->
imap
=
$rcmail
->
get_storage
();
$this
->
enabled
=
$rcmail
->
config
->
get
(
'kolab_cache'
,
false
);
if
(
$this
->
enabled
)
{
// always read folder cache and lock state from DB master
$this
->
db
->
set_table_dsn
(
'kolab_folders'
,
'w'
);
// remove sync-lock on script termination
$rcmail
->
add_shutdown_function
(
array
(
$this
,
'_sync_unlock'
));
}
if
(
$storage_folder
)
$this
->
set_folder
(
$storage_folder
);
}
/**
* Direct access to cache by folder_id
* (only for internal use)
*/
public
function
select_by_id
(
$folder_id
)
{
$folders_table
=
$this
->
db
->
table_name
(
'kolab_folders'
);
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$this
->
db
->
query
(
"SELECT * FROM $folders_table WHERE folder_id=?"
,
$folder_id
));
if
(
$sql_arr
)
{
$this
->
metadata
=
$sql_arr
;
$this
->
folder_id
=
$sql_arr
[
'folder_id'
];
$this
->
folder
=
new
StdClass
;
$this
->
folder
->
type
=
$sql_arr
[
'type'
];
$this
->
resource_uri
=
$sql_arr
[
'resource'
];
$this
->
cache_table
=
$this
->
db
->
table_name
(
'kolab_cache_'
.
$sql_arr
[
'type'
]);
$this
->
ready
=
true
;
}
}
/**
* Connect cache with a storage folder
*
* @param kolab_storage_folder The storage folder instance to connect with
*/
public
function
set_folder
(
kolab_storage_folder
$storage_folder
)
{
$this
->
folder
=
$storage_folder
;
if
(
empty
(
$this
->
folder
->
name
))
{
$this
->
ready
=
false
;
return
;
}
// compose fully qualified ressource uri for this instance
$this
->
resource_uri
=
$this
->
folder
->
get_resource_uri
();
$this
->
folders_table
=
$this
->
db
->
table_name
(
'kolab_folders'
);
$this
->
cache_table
=
$this
->
db
->
table_name
(
'kolab_cache_'
.
$this
->
folder
->
type
);
$this
->
ready
=
$this
->
enabled
&&
!
empty
(
$this
->
folder
->
type
);
$this
->
folder_id
=
null
;
}
/**
* Returns true if this cache supports query by type
*/
public
function
has_type_col
()
{
return
in_array
(
'type'
,
$this
->
extra_cols
);
}
/**
* Getter for the numeric ID used in cache tables
*/
public
function
get_folder_id
()
{
$this
->
_read_folder_data
();
return
$this
->
folder_id
;
}
/**
* Synchronize local cache data with remote
*/
public
function
synchronize
()
{
// only sync once per request cycle
if
(
$this
->
synched
)
return
;
// increase time limit
@
set_time_limit
(
$this
->
max_sync_lock_time
);
if
(!
$this
->
ready
)
{
// kolab cache is disabled, synchronize IMAP mailbox cache only
$this
->
imap
->
folder_sync
(
$this
->
folder
->
name
);
}
else
{
// read cached folder metadata
$this
->
_read_folder_data
();
// check cache status hash first ($this->metadata is set in _read_folder_data())
if
(
$this
->
metadata
[
'ctag'
]
!=
$this
->
folder
->
get_ctag
())
{
// lock synchronization for this folder or wait if locked
$this
->
_sync_lock
();
// disable messages cache if configured to do so
$this
->
bypass
(
true
);
// synchronize IMAP mailbox cache
$this
->
imap
->
folder_sync
(
$this
->
folder
->
name
);
// compare IMAP index with object cache index
$imap_index
=
$this
->
imap
->
index
(
$this
->
folder
->
name
,
null
,
null
,
true
,
true
);
// determine objects to fetch or to invalidate
if
(!
$imap_index
->
is_error
())
{
$imap_index
=
$imap_index
->
get
();
// read cache index
$sql_result
=
$this
->
db
->
query
(
"SELECT msguid, uid FROM $this->cache_table WHERE folder_id=?"
,
$this
->
folder_id
);
$old_index
=
array
();
while
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
$old_index
[]
=
$sql_arr
[
'msguid'
];
$this
->
uid2msg
[
$sql_arr
[
'uid'
]]
=
$sql_arr
[
'msguid'
];
}
// fetch new objects from imap
foreach
(
array_diff
(
$imap_index
,
$old_index
)
as
$msguid
)
{
if
(
$object
=
$this
->
folder
->
read_object
(
$msguid
,
'*'
))
{
$this
->
_extended_insert
(
$msguid
,
$object
);
}
}
$this
->
_extended_insert
(
0
,
null
);
// delete invalid entries from local DB
$del_index
=
array_diff
(
$old_index
,
$imap_index
);
if
(!
empty
(
$del_index
))
{
$quoted_ids
=
join
(
','
,
array_map
(
array
(
$this
->
db
,
'quote'
),
$del_index
));
$this
->
db
->
query
(
"DELETE FROM $this->cache_table WHERE folder_id=? AND msguid IN ($quoted_ids)"
,
$this
->
folder_id
);
}
// update ctag value (will be written to database in _sync_unlock())
$this
->
metadata
[
'ctag'
]
=
$this
->
folder
->
get_ctag
();
}
$this
->
bypass
(
false
);
// remove lock
$this
->
_sync_unlock
();
}
}
$this
->
synched
=
time
();
}
/**
* Read a single entry from cache or from IMAP directly
*
* @param string Related IMAP message UID
* @param string Object type to read
* @param string IMAP folder name the entry relates to
* @param array Hash array with object properties or null if not found
*/
public
function
get
(
$msguid
,
$type
=
null
,
$foldername
=
null
)
{
// delegate to another cache instance
if
(
$foldername
&&
$foldername
!=
$this
->
folder
->
name
)
{
return
kolab_storage
::
get_folder
(
$foldername
)->
cache
->
get
(
$msguid
,
$type
);
}
// load object if not in memory
if
(!
isset
(
$this
->
objects
[
$msguid
]))
{
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_result
=
$this
->
db
->
query
(
"SELECT * FROM $this->cache_table "
.
"WHERE folder_id=? AND msguid=?"
,
$this
->
folder_id
,
$msguid
);
if
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
$this
->
objects
=
array
(
$msguid
=>
$this
->
_unserialize
(
$sql_arr
));
// store only this object in memory (#2827)
}
}
// fetch from IMAP if not present in cache
if
(
empty
(
$this
->
objects
[
$msguid
]))
{
$result
=
$this
->
_fetch
(
array
(
$msguid
),
$type
,
$foldername
);
$this
->
objects
=
array
(
$msguid
=>
$result
[
0
]);
// store only this object in memory (#2827)
}
}
return
$this
->
objects
[
$msguid
];
}
/**
* Insert/Update a cache entry
*
* @param string Related IMAP message UID
* @param mixed Hash array with object properties to save or false to delete the cache entry
* @param string IMAP folder name the entry relates to
*/
public
function
set
(
$msguid
,
$object
,
$foldername
=
null
)
{
if
(!
$msguid
)
{
return
;
}
// delegate to another cache instance
if
(
$foldername
&&
$foldername
!=
$this
->
folder
->
name
)
{
kolab_storage
::
get_folder
(
$foldername
)->
cache
->
set
(
$msguid
,
$object
);
return
;
}
// remove old entry
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$this
->
db
->
query
(
"DELETE FROM $this->cache_table WHERE folder_id=? AND msguid=?"
,
$this
->
folder_id
,
$msguid
);
}
if
(
$object
)
{
// insert new object data...
$this
->
save
(
$msguid
,
$object
);
}
else
{
// ...or set in-memory cache to false
$this
->
objects
[
$msguid
]
=
$object
;
}
}
/**
* Insert (or update) a cache entry
*
* @param int Related IMAP message UID
* @param mixed Hash array with object properties to save or false to delete the cache entry
* @param int Optional old message UID (for update)
*/
public
function
save
(
$msguid
,
$object
,
$olduid
=
null
)
{
// write to cache
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_data
=
$this
->
_serialize
(
$object
);
$sql_data
[
'folder_id'
]
=
$this
->
folder_id
;
$sql_data
[
'msguid'
]
=
$msguid
;
$sql_data
[
'uid'
]
=
$object
[
'uid'
];
$args
=
array
();
$cols
=
array
(
'folder_id'
,
'msguid'
,
'uid'
,
'changed'
,
'data'
,
'xml'
,
'tags'
,
'words'
);
$cols
=
array_merge
(
$cols
,
$this
->
extra_cols
);
foreach
(
$cols
as
$idx
=>
$col
)
{
$cols
[
$idx
]
=
$this
->
db
->
quote_identifier
(
$col
);
$args
[]
=
$sql_data
[
$col
];
}
if
(
$olduid
)
{
foreach
(
$cols
as
$idx
=>
$col
)
{
$cols
[
$idx
]
=
"$col = ?"
;
}
$query
=
"UPDATE $this->cache_table SET "
.
implode
(
', '
,
$cols
)
.
" WHERE folder_id = ? AND msguid = ?"
;
$args
[]
=
$this
->
folder_id
;
$args
[]
=
$olduid
;
}
else
{
$query
=
"INSERT INTO $this->cache_table (created, "
.
implode
(
', '
,
$cols
)
.
") VALUES ("
.
$this
->
db
->
now
()
.
str_repeat
(
', ?'
,
count
(
$cols
))
.
")"
;
}
$result
=
$this
->
db
->
query
(
$query
,
$args
);
if
(!
$this
->
db
->
affected_rows
(
$result
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'type'
=>
'php'
,
'message'
=>
"Failed to write to kolab cache"
),
true
);
}
}
// keep a copy in memory for fast access
$this
->
objects
=
array
(
$msguid
=>
$object
);
$this
->
uid2msg
=
array
(
$object
[
'uid'
]
=>
$msguid
);
}
/**
* Move an existing cache entry to a new resource
*
* @param string Entry's IMAP message UID
* @param string Entry's Object UID
* @param string Target IMAP folder to move it to
*/
public
function
move
(
$msguid
,
$uid
,
$target_folder
)
{
if
(
$this
->
ready
)
{
$target
=
kolab_storage
::
get_folder
(
$target_folder
);
// resolve new message UID in target folder
if
(
$new_msguid
=
$target
->
cache
->
uid2msguid
(
$uid
))
{
$this
->
_read_folder_data
();
$this
->
db
->
query
(
"UPDATE $this->cache_table SET folder_id=?, msguid=? "
.
"WHERE folder_id=? AND msguid=?"
,
$target
->
cache
->
get_folder_id
(),
$new_msguid
,
$this
->
folder_id
,
$msguid
);
$result
=
$this
->
db
->
affected_rows
();
}
}
if
(
empty
(
$result
))
{
// just clear cache entry
$this
->
set
(
$msguid
,
false
);
}
unset
(
$this
->
uid2msg
[
$uid
]);
}
/**
* Remove all objects from local cache
*/
public
function
purge
(
$type
=
null
)
{
if
(!
$this
->
ready
)
{
return
true
;
}
$this
->
_read_folder_data
();
$result
=
$this
->
db
->
query
(
"DELETE FROM $this->cache_table WHERE folder_id=?"
,
$this
->
folder_id
);
return
$this
->
db
->
affected_rows
(
$result
);
}
/**
* Update resource URI for existing cache entries
*
* @param string Target IMAP folder to move it to
*/
public
function
rename
(
$new_folder
)
{
if
(!
$this
->
ready
)
{
return
;
}
$target
=
kolab_storage
::
get_folder
(
$new_folder
);
// resolve new message UID in target folder
$this
->
db
->
query
(
"UPDATE $this->folders_table SET resource=? "
.
"WHERE resource=?"
,
$target
->
get_resource_uri
(),
$this
->
resource_uri
);
}
/**
* Select Kolab objects filtered by the given query
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* triplet: array('<colname>', '<comparator>', '<value>')
* @param boolean Set true to only return UIDs instead of complete objects
* @return array List of Kolab data objects (each represented as hash array) or UIDs
*/
public
function
select
(
$query
=
array
(),
$uids
=
false
)
{
$result
=
$uids
?
array
()
:
new
kolab_storage_dataset
(
$this
);
// read from local cache DB (assume it to be synchronized)
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
// fetch full object data on one query if a small result set is expected
$fetchall
=
!
$uids
&&
(
$this
->
limit
?
$this
->
limit
[
0
]
:
$this
->
count
(
$query
))
<
500
;
$sql_query
=
"SELECT "
.
(
$fetchall
?
'*'
:
'msguid AS _msguid, uid'
)
.
" FROM $this->cache_table "
.
"WHERE folder_id=? "
.
$this
->
_sql_where
(
$query
);
if
(!
empty
(
$this
->
order_by
))
{
$sql_query
.=
' ORDER BY '
.
$this
->
order_by
;
}
$sql_result
=
$this
->
limit
?
$this
->
db
->
limitquery
(
$sql_query
,
$this
->
limit
[
1
],
$this
->
limit
[
0
],
$this
->
folder_id
)
:
$this
->
db
->
query
(
$sql_query
,
$this
->
folder_id
);
if
(
$this
->
db
->
is_error
(
$sql_result
))
{
if
(
$uids
)
{
return
null
;
}
$result
->
set_error
(
true
);
return
$result
;
}
while
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
if
(
$uids
)
{
$this
->
uid2msg
[
$sql_arr
[
'uid'
]]
=
$sql_arr
[
'_msguid'
];
$result
[]
=
$sql_arr
[
'uid'
];
}
else
if
(
$fetchall
&&
(
$object
=
$this
->
_unserialize
(
$sql_arr
)))
{
$result
[]
=
$object
;
}
else
{
// only add msguid to dataset index
$result
[]
=
$sql_arr
;
}
}
}
// use IMAP
else
{
$filter
=
$this
->
_query2assoc
(
$query
);
if
(
$filter
[
'type'
])
{
$search
=
'UNDELETED HEADER X-Kolab-Type '
.
kolab_format
::
KTYPE_PREFIX
.
$filter
[
'type'
];
$index
=
$this
->
imap
->
search_once
(
$this
->
folder
->
name
,
$search
);
}
else
{
$index
=
$this
->
imap
->
index
(
$this
->
folder
->
name
,
null
,
null
,
true
,
true
);
}
if
(
$index
->
is_error
())
{
if
(
$uids
)
{
return
null
;
}
$result
->
set_error
(
true
);
return
$result
;
}
$index
=
$index
->
get
();
$result
=
$uids
?
$index
:
$this
->
_fetch
(
$index
,
$filter
[
'type'
]);
// TODO: post-filter result according to query
}
// We don't want to cache big results in-memory, however
// if we select only one object here, there's a big chance we will need it later
if
(!
$uids
&&
count
(
$result
)
==
1
)
{
if
(
$msguid
=
$result
[
0
][
'_msguid'
])
{
$this
->
uid2msg
[
$result
[
0
][
'uid'
]]
=
$msguid
;
$this
->
objects
=
array
(
$msguid
=>
$result
[
0
]);
}
}
return
$result
;
}
/**
* Get number of objects mathing the given query
*
* @param array $query Pseudo-SQL query as list of filter parameter triplets
* @return integer The number of objects of the given type
*/
public
function
count
(
$query
=
array
())
{
// read from local cache DB (assume it to be synchronized)
if
(
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_result
=
$this
->
db
->
query
(
"SELECT COUNT(*) AS numrows FROM $this->cache_table "
.
"WHERE folder_id=? "
.
$this
->
_sql_where
(
$query
),
$this
->
folder_id
);
if
(
$this
->
db
->
is_error
(
$sql_result
))
{
return
null
;
}
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
);
$count
=
intval
(
$sql_arr
[
'numrows'
]);
}
// use IMAP
else
{
$filter
=
$this
->
_query2assoc
(
$query
);
if
(
$filter
[
'type'
])
{
$search
=
'UNDELETED HEADER X-Kolab-Type '
.
kolab_format
::
KTYPE_PREFIX
.
$filter
[
'type'
];
$index
=
$this
->
imap
->
search_once
(
$this
->
folder
->
name
,
$search
);
}
else
{
$index
=
$this
->
imap
->
index
(
$this
->
folder
->
name
,
null
,
null
,
true
,
true
);
}
if
(
$index
->
is_error
())
{
return
null
;
}
// TODO: post-filter result according to query
$count
=
$index
->
count
();
}
return
$count
;
}
/**
* Define ORDER BY clause for cache queries
*/
public
function
set_order_by
(
$sortcols
)
{
if
(!
empty
(
$sortcols
))
{
$this
->
order_by
=
join
(
', '
,
(
array
)
$sortcols
);
}
else
{
$this
->
order_by
=
null
;
}
}
/**
* Define LIMIT clause for cache queries
*/
public
function
set_limit
(
$length
,
$offset
=
0
)
{
$this
->
limit
=
array
(
$length
,
$offset
);
}
/**
* Helper method to compose a valid SQL query from pseudo filter triplets
*/
protected
function
_sql_where
(
$query
)
{
$sql_where
=
''
;
foreach
((
array
)
$query
as
$param
)
{
if
(
is_array
(
$param
[
0
]))
{
$subq
=
array
();
foreach
(
$param
[
0
]
as
$q
)
{
$subq
[]
=
preg_replace
(
'/^
\s
*AND
\s
+/i'
,
''
,
$this
->
_sql_where
(
array
(
$q
)));
}
if
(!
empty
(
$subq
))
{
$sql_where
.=
' AND ('
.
implode
(
$param
[
1
]
==
'OR'
?
' OR '
:
' AND '
,
$subq
)
.
')'
;
}
continue
;
}
else
if
(
$param
[
1
]
==
'='
&&
is_array
(
$param
[
2
]))
{
$qvalue
=
'('
.
join
(
','
,
array_map
(
array
(
$this
->
db
,
'quote'
),
$param
[
2
]))
.
')'
;
$param
[
1
]
=
'IN'
;
}
else
if
(
$param
[
1
]
==
'~'
||
$param
[
1
]
==
'LIKE'
||
$param
[
1
]
==
'!~'
||
$param
[
1
]
==
'!LIKE'
)
{
$not
=
(
$param
[
1
]
==
'!~'
||
$param
[
1
]
==
'!LIKE'
)
?
'NOT '
:
''
;
$param
[
1
]
=
$not
.
'LIKE'
;
$qvalue
=
$this
->
db
->
quote
(
'%'
.
preg_replace
(
'/(^
\^
|
\$
$)/'
,
' '
,
$param
[
2
]).
'%'
);
}
else
if
(
$param
[
0
]
==
'tags'
)
{
$param
[
1
]
=
(
$param
[
1
]
==
'!='
?
'NOT '
:
''
)
.
'LIKE'
;
$qvalue
=
$this
->
db
->
quote
(
'% '
.
$param
[
2
].
' %'
);
}
else
{
$qvalue
=
$this
->
db
->
quote
(
$param
[
2
]);
}
$sql_where
.=
sprintf
(
' AND %s %s %s'
,
$this
->
db
->
quote_identifier
(
$param
[
0
]),
$param
[
1
],
$qvalue
);
}
return
$sql_where
;
}
/**
* Helper method to convert the given pseudo-query triplets into
* an associative filter array with 'equals' values only
*/
protected
function
_query2assoc
(
$query
)
{
// extract object type from query parameter
$filter
=
array
();
foreach
(
$query
as
$param
)
{
if
(
$param
[
1
]
==
'='
)
$filter
[
$param
[
0
]]
=
$param
[
2
];
}
return
$filter
;
}
/**
* Fetch messages from IMAP
*
* @param array List of message UIDs to fetch
* @param string Requested object type or * for all
* @param string IMAP folder to read from
* @return array List of parsed Kolab objects
*/
protected
function
_fetch
(
$index
,
$type
=
null
,
$folder
=
null
)
{
$results
=
new
kolab_storage_dataset
(
$this
);
foreach
((
array
)
$index
as
$msguid
)
{
if
(
$object
=
$this
->
folder
->
read_object
(
$msguid
,
$type
,
$folder
))
{
$results
[]
=
$object
;
$this
->
set
(
$msguid
,
$object
);
}
}
return
$results
;
}
/**
* Helper method to convert the given Kolab object into a dataset to be written to cache
*/
protected
function
_serialize
(
$object
)
{
$sql_data
=
array
(
'changed'
=>
null
,
'xml'
=>
''
,
'tags'
=>
''
,
'words'
=>
''
);
if
(
$object
[
'changed'
])
{
$sql_data
[
'changed'
]
=
date
(
'Y-m-d H:i:s'
,
is_object
(
$object
[
'changed'
])
?
$object
[
'changed'
]->
format
(
'U'
)
:
$object
[
'changed'
]);
}
if
(
$object
[
'_formatobj'
])
{
$sql_data
[
'xml'
]
=
preg_replace
(
'!(</?[a-z0-9:-]+>)[
\n\r\t\s
]+!ms'
,
'$1'
,
(
string
)
$object
[
'_formatobj'
]->
write
(
3.0
));
$sql_data
[
'tags'
]
=
' '
.
join
(
' '
,
$object
[
'_formatobj'
]->
get_tags
())
.
' '
;
// pad with spaces for strict/prefix search
$sql_data
[
'words'
]
=
' '
.
join
(
' '
,
$object
[
'_formatobj'
]->
get_words
())
.
' '
;
}
// extract object data
$data
=
array
();
foreach
(
$object
as
$key
=>
$val
)
{
// skip empty properties
if
(
$val
===
""
||
$val
===
null
)
{
continue
;
}
// mark binary data to be extracted from xml on unserialize()
if
(
isset
(
$this
->
binary_items
[
$key
]))
{
$data
[
$key
]
=
true
;
}
else
if
(
$key
[
0
]
!=
'_'
)
{
$data
[
$key
]
=
$val
;
}
else
if
(
$key
==
'_attachments'
)
{
foreach
(
$val
as
$k
=>
$att
)
{
unset
(
$att
[
'content'
],
$att
[
'path'
]);
if
(
$att
[
'id'
])
$data
[
$key
][
$k
]
=
$att
;
}
}
}
// use base64 encoding (Bug #1912, #2662)
$sql_data
[
'data'
]
=
base64_encode
(
serialize
(
$data
));
return
$sql_data
;
}
/**
* Helper method to turn stored cache data into a valid storage object
*/
protected
function
_unserialize
(
$sql_arr
)
{
// check if data is a base64-encoded string, for backward compat.
if
(
strpos
(
substr
(
$sql_arr
[
'data'
],
0
,
64
),
':'
)
===
false
)
{
$sql_arr
[
'data'
]
=
base64_decode
(
$sql_arr
[
'data'
]);
}
$object
=
unserialize
(
$sql_arr
[
'data'
]);
// de-serialization failed
if
(
$object
===
false
)
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'type'
=>
'php'
,
'message'
=>
"Malformed data for {$this->resource_uri}/{$sql_arr['msguid']} object."
),
true
);
return
null
;
}
// decode binary properties
foreach
(
$this
->
binary_items
as
$key
=>
$regexp
)
{
if
(!
empty
(
$object
[
$key
])
&&
preg_match
(
$regexp
,
$sql_arr
[
'xml'
],
$m
))
{
$object
[
$key
]
=
base64_decode
(
$m
[
1
]);
}
}
// add meta data
$object
[
'_type'
]
=
$sql_arr
[
'type'
]
?:
$this
->
folder
->
type
;
$object
[
'_msguid'
]
=
$sql_arr
[
'msguid'
];
$object
[
'_mailbox'
]
=
$this
->
folder
->
name
;
$object
[
'_size'
]
=
strlen
(
$sql_arr
[
'xml'
]);
$object
[
'_formatobj'
]
=
kolab_format
::
factory
(
$object
[
'_type'
],
3.0
,
$sql_arr
[
'xml'
]);
return
$object
;
}
/**
* Write records into cache using extended inserts to reduce the number of queries to be executed
*
* @param int Message UID. Set 0 to commit buffered inserts
* @param array Kolab object to cache
*/
protected
function
_extended_insert
(
$msguid
,
$object
)
{
static
$buffer
=
''
;
$line
=
''
;
if
(
$object
)
{
$sql_data
=
$this
->
_serialize
(
$object
);
$values
=
array
(
$this
->
db
->
quote
(
$this
->
folder_id
),
$this
->
db
->
quote
(
$msguid
),
$this
->
db
->
quote
(
$object
[
'uid'
]),
$this
->
db
->
now
(),
$this
->
db
->
quote
(
$sql_data
[
'changed'
]),
$this
->
db
->
quote
(
$sql_data
[
'data'
]),
$this
->
db
->
quote
(
$sql_data
[
'xml'
]),
$this
->
db
->
quote
(
$sql_data
[
'tags'
]),
$this
->
db
->
quote
(
$sql_data
[
'words'
]),
);
foreach
(
$this
->
extra_cols
as
$col
)
{
$values
[]
=
$this
->
db
->
quote
(
$sql_data
[
$col
]);
}
$line
=
'('
.
join
(
','
,
$values
)
.
')'
;
}
if
(
$buffer
&&
(!
$msguid
||
(
strlen
(
$buffer
)
+
strlen
(
$line
)
>
$this
->
max_sql_packet
())))
{
$extra_cols
=
$this
->
extra_cols
?
', '
.
join
(
', '
,
$this
->
extra_cols
)
:
''
;
$result
=
$this
->
db
->
query
(
"INSERT INTO $this->cache_table "
.
" (folder_id, msguid, uid, created, changed, data, xml, tags, words $extra_cols)"
.
" VALUES $buffer"
);
if
(!
$this
->
db
->
affected_rows
(
$result
))
{
rcube
::
raise_error
(
array
(
'code'
=>
900
,
'type'
=>
'php'
,
'message'
=>
"Failed to write to kolab cache"
),
true
);
}
$buffer
=
''
;
}
$buffer
.=
(
$buffer
?
','
:
''
)
.
$line
;
}
/**
* Returns max_allowed_packet from mysql config
*/
protected
function
max_sql_packet
()
{
if
(!
$this
->
max_sql_packet
)
{
// mysql limit or max 4 MB
$value
=
$this
->
db
->
get_variable
(
'max_allowed_packet'
,
1048500
);
$this
->
max_sql_packet
=
min
(
$value
,
4
*
1024
*
1024
)
-
2000
;
}
return
$this
->
max_sql_packet
;
}
/**
* Read this folder's ID and cache metadata
*/
protected
function
_read_folder_data
()
{
// already done
if
(!
empty
(
$this
->
folder_id
)
||
!
$this
->
ready
)
return
;
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$this
->
db
->
query
(
"SELECT folder_id, synclock, ctag FROM $this->folders_table WHERE resource=?"
,
$this
->
resource_uri
));
if
(
$sql_arr
)
{
$this
->
metadata
=
$sql_arr
;
$this
->
folder_id
=
$sql_arr
[
'folder_id'
];
}
else
{
$this
->
db
->
query
(
"INSERT INTO $this->folders_table (resource, type) VALUES (?, ?)"
,
$this
->
resource_uri
,
$this
->
folder
->
type
);
$this
->
folder_id
=
$this
->
db
->
insert_id
(
'kolab_folders'
);
$this
->
metadata
=
array
();
}
}
/**
* Check lock record for this folder and wait if locked or set lock
*/
protected
function
_sync_lock
()
{
if
(!
$this
->
ready
)
return
;
$this
->
_read_folder_data
();
$sql_query
=
"SELECT synclock, ctag FROM $this->folders_table WHERE folder_id=?"
;
// abort if database is not set-up
if
(
$this
->
db
->
is_error
())
{
$this
->
ready
=
false
;
return
;
}
$this
->
synclock
=
true
;
// wait if locked (expire locks after 10 minutes)
while
(
$this
->
metadata
&&
intval
(
$this
->
metadata
[
'synclock'
])
>
0
&&
$this
->
metadata
[
'synclock'
]
+
$this
->
max_sync_lock_time
>
time
())
{
usleep
(
500000
);
$this
->
metadata
=
$this
->
db
->
fetch_assoc
(
$this
->
db
->
query
(
$sql_query
,
$this
->
folder_id
));
}
// set lock
$this
->
db
->
query
(
"UPDATE $this->folders_table SET synclock = ? WHERE folder_id = ?"
,
time
(),
$this
->
folder_id
);
}
/**
* Remove lock for this folder
*/
public
function
_sync_unlock
()
{
if
(!
$this
->
ready
||
!
$this
->
synclock
)
return
;
$this
->
db
->
query
(
"UPDATE $this->folders_table SET synclock = 0, ctag = ? WHERE folder_id = ?"
,
$this
->
metadata
[
'ctag'
],
$this
->
folder_id
);
$this
->
synclock
=
false
;
}
/**
* Resolve an object UID into an IMAP message UID
*
* @param string Kolab object UID
* @param boolean Include deleted objects
* @return int The resolved IMAP message UID
*/
public
function
uid2msguid
(
$uid
,
$deleted
=
false
)
{
// query local database if available
if
(!
isset
(
$this
->
uid2msg
[
$uid
])
&&
$this
->
ready
)
{
$this
->
_read_folder_data
();
$sql_result
=
$this
->
db
->
query
(
"SELECT msguid FROM $this->cache_table "
.
"WHERE folder_id=? AND uid=?"
,
$this
->
folder_id
,
$uid
);
if
(
$sql_arr
=
$this
->
db
->
fetch_assoc
(
$sql_result
))
{
$this
->
uid2msg
[
$uid
]
=
$sql_arr
[
'msguid'
];
}
}
if
(!
isset
(
$this
->
uid2msg
[
$uid
]))
{
// use IMAP SEARCH to get the right message
$index
=
$this
->
imap
->
search_once
(
$this
->
folder
->
name
,
(
$deleted
?
''
:
'UNDELETED '
)
.
'HEADER SUBJECT '
.
rcube_imap_generic
::
escape
(
$uid
));
$results
=
$index
->
get
();
$this
->
uid2msg
[
$uid
]
=
$results
[
0
];
}
return
$this
->
uid2msg
[
$uid
];
}
/**
* Getter for protected member variables
*/
public
function
__get
(
$name
)
{
if
(
$name
==
'folder_id'
)
{
$this
->
_read_folder_data
();
}
return
$this
->
$name
;
}
/**
* Bypass Roundcube messages cache.
* Roundcube cache duplicates information already stored in kolab_cache.
*
* @param bool $disable True disables, False enables messages cache
*/
public
function
bypass
(
$disable
=
false
)
{
// if kolab cache is disabled do nothing
if
(!
$this
->
enabled
)
{
return
;
}
static
$messages_cache
,
$cache_bypass
;
if
(
$messages_cache
===
null
)
{
$rcmail
=
rcube
::
get_instance
();
$messages_cache
=
(
bool
)
$rcmail
->
config
->
get
(
'messages_cache'
);
$cache_bypass
=
(
int
)
$rcmail
->
config
->
get
(
'kolab_messages_cache_bypass'
);
}
if
(
$messages_cache
)
{
// handle recurrent (multilevel) bypass() calls
if
(
$disable
)
{
$this
->
cache_bypassed
+=
1
;
if
(
$this
->
cache_bypassed
>
1
)
{
return
;
}
}
else
{
$this
->
cache_bypassed
-=
1
;
if
(
$this
->
cache_bypassed
>
0
)
{
return
;
}
}
switch
(
$cache_bypass
)
{
case
2
:
// Disable messages cache completely
$this
->
imap
->
set_messages_caching
(!
$disable
);
break
;
case
1
:
// We'll disable messages cache, but keep index cache.
// Default mode is both (MODE_INDEX | MODE_MESSAGE)
$mode
=
rcube_imap_cache
::
MODE_INDEX
;
if
(!
$disable
)
{
$mode
|=
rcube_imap_cache
::
MODE_MESSAGE
;
}
$this
->
imap
->
set_messages_caching
(
true
,
$mode
);
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Apr 4, 3:27 AM (1 d, 14 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
fc/ae/1d47cddddd56606cc10560fb2ad7
Default Alt Text
kolab_storage_cache.php (32 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline