- Rewritten messages caching (merged devel-mcache branch):

Indexes are stored in a separate table, so there's no need to store all messages in a folder
  Added threads data caching
  Flags are stored separately, so flag change doesn't cause DELETE+INSERT, just UPDATE
- Partial QRESYNC support
- Improved FETCH response handling
- Improvements in response tokenization method
release-0.7
alecpl 13 years ago
parent b104e39f34
commit 80152b333c

@ -1,6 +1,13 @@
CHANGELOG Roundcube Webmail
===========================
- Rewritten messages caching:
Indexes are stored in a separate table, so there's no need to store all messages in a folder
Added threads data caching
Flags are stored separately, so flag change doesn't cause DELETE+INSERT, just UPDATE
- Partial QRESYNC support
- Improved FETCH response handling
- Improvements in response tokenization method
- Use 'From' and 'To' labels instead of 'Sender' and 'Recipient'
- Fix username case-insensitivity issue in MySQL (#1488021)
- Addressbook Saved Searches

@ -7,6 +7,37 @@ CREATE TABLE [dbo].[cache] (
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[cache_index] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[cache_thread] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[cache_messages] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[uid] [int] NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
[seen] [char](1) NOT NULL ,
[deleted] [char](1) NOT NULL ,
[answered] [char](1) NOT NULL ,
[forwarded] [char](1) NOT NULL ,
[flagged] [char](1) NOT NULL ,
[mdnsent] [char](1) NOT NULL ,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[contacts] (
[contact_id] [int] IDENTITY (1, 1) NOT NULL ,
[user_id] [int] NOT NULL ,
@ -53,25 +84,6 @@ CREATE TABLE [dbo].[identities] (
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[messages] (
[message_id] [int] IDENTITY (1, 1) NOT NULL ,
[user_id] [int] NOT NULL ,
[del] [tinyint] NOT NULL ,
[cache_key] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[created] [datetime] NOT NULL ,
[idx] [int] NOT NULL ,
[uid] [int] NOT NULL ,
[subject] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,
[from] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,
[to] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,
[cc] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,
[date] [datetime] NOT NULL ,
[size] [int] NOT NULL ,
[headers] [text] COLLATE Latin1_General_CI_AI NOT NULL ,
[structure] [text] COLLATE Latin1_General_CI_AI NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[session] (
[sess_id] [varchar] (32) COLLATE Latin1_General_CI_AI NOT NULL ,
[created] [datetime] NOT NULL ,
@ -116,6 +128,27 @@ ALTER TABLE [dbo].[cache] WITH NOCHECK ADD
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_index] WITH NOCHECK ADD
PRIMARY KEY CLUSTERED
(
[user_id],[mailbox]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_thread] WITH NOCHECK ADD
PRIMARY KEY CLUSTERED
(
[user_id],[mailbox]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_messages] WITH NOCHECK ADD
PRIMARY KEY CLUSTERED
(
[user_id],[mailbox],[uid]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[contacts] WITH NOCHECK ADD
CONSTRAINT [PK_contacts_contact_id] PRIMARY KEY CLUSTERED
(
@ -144,13 +177,6 @@ ALTER TABLE [dbo].[identities] WITH NOCHECK ADD
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[messages] WITH NOCHECK ADD
PRIMARY KEY CLUSTERED
(
[message_id]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[session] WITH NOCHECK ADD
CONSTRAINT [PK_session_sess_id] PRIMARY KEY CLUSTERED
(
@ -187,6 +213,33 @@ GO
CREATE INDEX [IX_cache_created] ON [dbo].[cache]([created]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_index] ADD
CONSTRAINT [DF_cache_index_changed] DEFAULT (getdate()) FOR [changed]
GO
CREATE INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_thread] ADD
CONSTRAINT [DF_cache_thread_changed] DEFAULT (getdate()) FOR [changed]
GO
CREATE INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_messages] ADD
CONSTRAINT [DF_cache_messages_changed] DEFAULT (getdate()) FOR [changed]
CONSTRAINT [DF_cache_messages_seen] DEFAULT (0) FOR [seen],
CONSTRAINT [DF_cache_messages_deleted] DEFAULT (0) FOR [deleted],
CONSTRAINT [DF_cache_messages_answered] DEFAULT (0) FOR [answered],
CONSTRAINT [DF_cache_messages_forwarded] DEFAULT (0) FOR [forwarded],
CONSTRAINT [DF_cache_messages_flagged] DEFAULT (0) FOR [flagged],
CONSTRAINT [DF_cache_messages_mdnsent] DEFAULT (0) FOR [mdnsent],
GO
CREATE INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[contacts] ADD
CONSTRAINT [DF_contacts_user_id] DEFAULT (0) FOR [user_id],
CONSTRAINT [DF_contacts_changed] DEFAULT (getdate()) FOR [changed],
@ -238,33 +291,6 @@ GO
CREATE INDEX [IX_identities_user_id] ON [dbo].[identities]([user_id]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[messages] ADD
CONSTRAINT [DF_messages_user_id] DEFAULT (0) FOR [user_id],
CONSTRAINT [DF_messages_del] DEFAULT (0) FOR [del],
CONSTRAINT [DF_messages_cache_key] DEFAULT ('') FOR [cache_key],
CONSTRAINT [DF_messages_created] DEFAULT (getdate()) FOR [created],
CONSTRAINT [DF_messages_idx] DEFAULT (0) FOR [idx],
CONSTRAINT [DF_messages_uid] DEFAULT (0) FOR [uid],
CONSTRAINT [DF_messages_subject] DEFAULT ('') FOR [subject],
CONSTRAINT [DF_messages_from] DEFAULT ('') FOR [from],
CONSTRAINT [DF_messages_to] DEFAULT ('') FOR [to],
CONSTRAINT [DF_messages_cc] DEFAULT ('') FOR [cc],
CONSTRAINT [DF_messages_date] DEFAULT (getdate()) FOR [date],
CONSTRAINT [DF_messages_size] DEFAULT (0) FOR [size]
GO
CREATE INDEX [IX_messages_user_id] ON [dbo].[messages]([user_id]) ON [PRIMARY]
GO
CREATE INDEX [IX_messages_cache_key] ON [dbo].[messages]([cache_key]) ON [PRIMARY]
GO
CREATE INDEX [IX_messages_uid] ON [dbo].[messages]([uid]) ON [PRIMARY]
GO
CREATE INDEX [IX_messages_created] ON [dbo].[messages]([created]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[session] ADD
CONSTRAINT [DF_session_sess_id] DEFAULT ('') FOR [sess_id],
CONSTRAINT [DF_session_created] DEFAULT (getdate()) FOR [created],
@ -318,7 +344,17 @@ ALTER TABLE [dbo].[cache] ADD CONSTRAINT [FK_cache_user_id]
ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[messages] ADD CONSTRAINT [FK_messages_user_id]
ALTER TABLE [dbo].[cache_index] ADD CONSTRAINT [FK_cache_index_user_id]
FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id])
ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[cache_thread] ADD CONSTRAINT [FK_cache_thread_user_id]
FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id])
ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[cache_messages] ADD CONSTRAINT [FK_cache_messages_user_id]
FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id])
ON DELETE CASCADE ON UPDATE CASCADE
GO

@ -151,3 +151,99 @@ ALTER TABLE [dbo].[searches] ADD CONSTRAINT [FK_searches_user_id]
ON DELETE CASCADE ON UPDATE CASCADE
GO
DROP TABLE [dbo].[messages]
GO
CREATE TABLE [dbo].[cache_index] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[cache_thread] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[cache_messages] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[uid] [int] NOT NULL ,
[changed] [datetime] NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
[seen] [char](1) NOT NULL ,
[deleted] [char](1) NOT NULL ,
[answered] [char](1) NOT NULL ,
[forwarded] [char](1) NOT NULL ,
[flagged] [char](1) NOT NULL ,
[mdnsent] [char](1) NOT NULL ,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_index] WITH NOCHECK ADD
PRIMARY KEY CLUSTERED
(
[user_id],[mailbox]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_thread] WITH NOCHECK ADD
PRIMARY KEY CLUSTERED
(
[user_id],[mailbox]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_messages] WITH NOCHECK ADD
PRIMARY KEY CLUSTERED
(
[user_id],[mailbox],[uid]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_index] ADD
CONSTRAINT [DF_cache_index_changed] DEFAULT (getdate()) FOR [changed]
GO
CREATE INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_thread] ADD
CONSTRAINT [DF_cache_thread_changed] DEFAULT (getdate()) FOR [changed]
GO
CREATE INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_messages] ADD
CONSTRAINT [DF_cache_messages_changed] DEFAULT (getdate()) FOR [changed]
CONSTRAINT [DF_cache_messages_seen] DEFAULT (0) FOR [seen],
CONSTRAINT [DF_cache_messages_deleted] DEFAULT (0) FOR [deleted],
CONSTRAINT [DF_cache_messages_answered] DEFAULT (0) FOR [answered],
CONSTRAINT [DF_cache_messages_forwarded] DEFAULT (0) FOR [forwarded],
CONSTRAINT [DF_cache_messages_flagged] DEFAULT (0) FOR [flagged],
CONSTRAINT [DF_cache_messages_mdnsent] DEFAULT (0) FOR [mdnsent],
GO
CREATE INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[cache_index] ADD CONSTRAINT [FK_cache_index_user_id]
FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id])
ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[cache_thread] ADD CONSTRAINT [FK_cache_thread_user_id]
FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id])
ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[cache_messages] ADD CONSTRAINT [FK_cache_messages_user_id]
FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id])
ON DELETE CASCADE ON UPDATE CASCADE
GO

@ -33,33 +33,6 @@ CREATE TABLE `users` (
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `messages`
CREATE TABLE `messages` (
`message_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`del` tinyint(1) NOT NULL DEFAULT '0',
`cache_key` varchar(128) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL,
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`idx` int(11) UNSIGNED NOT NULL DEFAULT '0',
`uid` int(11) UNSIGNED NOT NULL DEFAULT '0',
`subject` varchar(255) NOT NULL,
`from` varchar(255) NOT NULL,
`to` varchar(255) NOT NULL,
`cc` varchar(255) NOT NULL,
`date` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`size` int(11) UNSIGNED NOT NULL DEFAULT '0',
`headers` text NOT NULL,
`structure` text,
PRIMARY KEY(`message_id`),
CONSTRAINT `user_id_fk_messages` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `created_index` (`created`),
INDEX `index_index` (`user_id`, `cache_key`, `idx`),
UNIQUE `uniqueness` (`user_id`, `cache_key`, `uid`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache`
CREATE TABLE `cache` (
@ -76,6 +49,55 @@ CREATE TABLE `cache` (
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_index`
CREATE TABLE `cache_index` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`mailbox` varchar(255) BINARY NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_cache_index` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `changed_index` (`changed`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_thread`
CREATE TABLE `cache_thread` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`mailbox` varchar(255) BINARY NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_cache_thread` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `changed_index` (`changed`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_messages`
CREATE TABLE `cache_messages` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`mailbox` varchar(255) BINARY NOT NULL,
`uid` int(11) UNSIGNED NOT NULL DEFAULT '0',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
`seen` tinyint(1) NOT NULL DEFAULT '0',
`deleted` tinyint(1) NOT NULL DEFAULT '0',
`answered` tinyint(1) NOT NULL DEFAULT '0',
`forwarded` tinyint(1) NOT NULL DEFAULT '0',
`flagged` tinyint(1) NOT NULL DEFAULT '0',
`mdnsent` tinyint(1) NOT NULL DEFAULT '0',
CONSTRAINT `user_id_fk_cache_messages` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `changed_index` (`changed`),
PRIMARY KEY (`user_id`, `mailbox`, `uid`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `contacts`
CREATE TABLE `contacts` (

@ -170,3 +170,45 @@ CREATE TABLE `searches` (
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE `uniqueness` (`user_id`, `type`, `name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
DROP TABLE `messages`;
CREATE TABLE `cache_index` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`mailbox` varchar(255) BINARY NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_cache_index` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `changed_index` (`changed`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE `cache_thread` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`mailbox` varchar(255) BINARY NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_cache_thread` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `changed_index` (`changed`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE `cache_messages` (
`user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
`mailbox` varchar(255) BINARY NOT NULL,
`uid` int(11) UNSIGNED NOT NULL DEFAULT '0',
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`data` longtext NOT NULL,
`seen` tinyint(1) NOT NULL DEFAULT '0',
`deleted` tinyint(1) NOT NULL DEFAULT '0',
`answered` tinyint(1) NOT NULL DEFAULT '0',
`forwarded` tinyint(1) NOT NULL DEFAULT '0',
`flagged` tinyint(1) NOT NULL DEFAULT '0',
`mdnsent` tinyint(1) NOT NULL DEFAULT '0',
CONSTRAINT `user_id_fk_cache_messages` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `changed_index` (`changed`),
PRIMARY KEY (`user_id`, `mailbox`, `uid`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;

@ -67,7 +67,7 @@ CREATE SEQUENCE identity_ids
CREATE TABLE identities (
identity_id integer DEFAULT nextval('identity_ids'::text) PRIMARY KEY,
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
changed timestamp with time zone DEFAULT now() NOT NULL,
del smallint DEFAULT 0 NOT NULL,
standard smallint DEFAULT 0 NOT NULL,
@ -178,7 +178,7 @@ CREATE SEQUENCE cache_ids
CREATE TABLE "cache" (
cache_id integer DEFAULT nextval('cache_ids'::text) PRIMARY KEY,
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
cache_key varchar(128) DEFAULT '' NOT NULL,
created timestamp with time zone DEFAULT now() NOT NULL,
data text NOT NULL
@ -188,43 +188,59 @@ CREATE INDEX cache_user_id_idx ON "cache" (user_id, cache_key);
CREATE INDEX cache_created_idx ON "cache" (created);
--
-- Sequence "message_ids"
-- Name: message_ids; Type: SEQUENCE; Schema: public; Owner: postgres
-- Table "cache_index"
-- Name: cache_index; Type: TABLE; Schema: public; Owner: postgres
--
CREATE SEQUENCE message_ids
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
CREATE TABLE cache_index (
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
mailbox varchar(255) NOT NULL,
changed timestamp with time zone DEFAULT now() NOT NULL,
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX cache_index_changed_idx ON cache_index (changed);
--
-- Table "messages"
-- Name: messages; Type: TABLE; Schema: public; Owner: postgres
-- Table "cache_thread"
-- Name: cache_thread; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE messages (
message_id integer DEFAULT nextval('message_ids'::text) PRIMARY KEY,
CREATE TABLE cache_thread (
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
del smallint DEFAULT 0 NOT NULL,
cache_key varchar(128) DEFAULT '' NOT NULL,
created timestamp with time zone DEFAULT now() NOT NULL,
idx integer DEFAULT 0 NOT NULL,
uid integer DEFAULT 0 NOT NULL,
subject varchar(128) DEFAULT '' NOT NULL,
"from" varchar(128) DEFAULT '' NOT NULL,
"to" varchar(128) DEFAULT '' NOT NULL,
cc varchar(128) DEFAULT '' NOT NULL,
date timestamp with time zone NOT NULL,
size integer DEFAULT 0 NOT NULL,
headers text NOT NULL,
structure text,
CONSTRAINT messages_user_id_key UNIQUE (user_id, cache_key, uid)
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
mailbox varchar(255) NOT NULL,
changed timestamp with time zone DEFAULT now() NOT NULL,
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX cache_thread_changed_idx ON cache_thread (changed);
--
-- Table "cache_messages"
-- Name: cache_messages; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE cache_messages (
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
mailbox varchar(255) NOT NULL,
uid integer NOT NULL,
changed timestamp with time zone DEFAULT now() NOT NULL,
data text NOT NULL,
seen smallint NOT NULL DEFAULT 0,
deleted smallint NOT NULL DEFAULT 0,
answered smallint NOT NULL DEFAULT 0,
forwarded smallint NOT NULL DEFAULT 0,
flagged smallint NOT NULL DEFAULT 0,
mdnsent smallint NOT NULL DEFAULT 0,
PRIMARY KEY (user_id, mailbox, uid)
);
CREATE INDEX messages_index_idx ON messages (user_id, cache_key, idx);
CREATE INDEX messages_created_idx ON messages (created);
CREATE INDEX cache_messages_changed_idx ON cache_messages (changed);
--
-- Table "dictionary"

@ -126,3 +126,46 @@ CREATE TABLE searches (
data text NOT NULL,
CONSTRAINT searches_user_id_key UNIQUE (user_id, "type", name)
);
DROP SEQUENCE messages_ids;
DROP TABLE messages;
CREATE TABLE cache_index (
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
mailbox varchar(255) NOT NULL,
changed timestamp with time zone DEFAULT now() NOT NULL,
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX cache_index_changed_idx ON cache_index (changed);
CREATE TABLE cache_thread (
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
mailbox varchar(255) NOT NULL,
changed timestamp with time zone DEFAULT now() NOT NULL,
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX cache_thread_changed_idx ON cache_thread (changed);
CREATE TABLE cache_messages (
user_id integer NOT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
mailbox varchar(255) NOT NULL,
uid integer NOT NULL,
changed timestamp with time zone DEFAULT now() NOT NULL,
data text NOT NULL,
seen smallint NOT NULL DEFAULT 0,
deleted smallint NOT NULL DEFAULT 0,
answered smallint NOT NULL DEFAULT 0,
forwarded smallint NOT NULL DEFAULT 0,
flagged smallint NOT NULL DEFAULT 0,
mdnsent smallint NOT NULL DEFAULT 0,
PRIMARY KEY (user_id, mailbox, uid)
);
CREATE INDEX cache_messages_changed_idx ON cache_messages (changed);

@ -1,7 +1,7 @@
-- Roundcube Webmail initial database structure
--
-- Table structure for table `cache`
-- Table structure for table cache
--
CREATE TABLE cache (
@ -9,7 +9,7 @@ CREATE TABLE cache (
user_id integer NOT NULL default 0,
cache_key varchar(128) NOT NULL default '',
created datetime NOT NULL default '0000-00-00 00:00:00',
data longtext NOT NULL
data text NOT NULL
);
CREATE INDEX ix_cache_user_cache_key ON cache(user_id, cache_key);
@ -121,34 +121,6 @@ CREATE INDEX ix_session_changed ON session (changed);
-- --------------------------------------------------------
--
-- Table structure for table messages
--
CREATE TABLE messages (
message_id integer NOT NULL PRIMARY KEY,
user_id integer NOT NULL default '0',
del tinyint NOT NULL default '0',
cache_key varchar(128) NOT NULL default '',
created datetime NOT NULL default '0000-00-00 00:00:00',
idx integer NOT NULL default '0',
uid integer NOT NULL default '0',
subject varchar(255) NOT NULL default '',
"from" varchar(255) NOT NULL default '',
"to" varchar(255) NOT NULL default '',
"cc" varchar(255) NOT NULL default '',
"date" datetime NOT NULL default '0000-00-00 00:00:00',
size integer NOT NULL default '0',
headers text NOT NULL,
structure text
);
CREATE UNIQUE INDEX ix_messages_user_cache_uid ON messages (user_id,cache_key,uid);
CREATE INDEX ix_messages_index ON messages (user_id,cache_key,idx);
CREATE INDEX ix_messages_created ON messages (created);
-- --------------------------------------------------------
--
-- Table structure for table dictionary
--
@ -176,3 +148,58 @@ CREATE TABLE searches (
);
CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name);
-- --------------------------------------------------------
--
-- Table structure for table cache_index
--
CREATE TABLE cache_index (
user_id integer NOT NULL,
mailbox varchar(255) NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX ix_cache_index_changed ON cache_index (changed);
-- --------------------------------------------------------
--
-- Table structure for table cache_thread
--
CREATE TABLE cache_thread (
user_id integer NOT NULL,
mailbox varchar(255) NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX ix_cache_thread_changed ON cache_thread (changed);
-- --------------------------------------------------------
--
-- Table structure for table cache_messages
--
CREATE TABLE cache_messages (
user_id integer NOT NULL,
mailbox varchar(255) NOT NULL,
uid integer NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
data text NOT NULL,
seen smallint NOT NULL DEFAULT '0',
deleted smallint NOT NULL DEFAULT '0',
answered smallint NOT NULL DEFAULT '0',
forwarded smallint NOT NULL DEFAULT '0',
flagged smallint NOT NULL DEFAULT '0',
mdnsent smallint NOT NULL DEFAULT '0',
PRIMARY KEY (user_id, mailbox, uid)
);
CREATE INDEX ix_cache_messages_changed ON cache_messages (changed);

@ -223,11 +223,11 @@ INSERT INTO contacts (contact_id, user_id, changed, del, name, email, firstname,
CREATE INDEX ix_contacts_user_id ON contacts(user_id, email);
DROP TABLE contacts_tmp;
DELETE FROM messages;
DELETE FROM cache;
CREATE INDEX ix_contactgroupmembers_contact_id ON contactgroupmembers (contact_id);
-- Updates from version 0.6-stable
CREATE TABLE dictionary (
@ -247,3 +247,42 @@ CREATE TABLE searches (
);
CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name);
DROP TABLE messages;
CREATE TABLE cache_index (
user_id integer NOT NULL,
mailbox varchar(255) NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX ix_cache_index_changed ON cache_index (changed);
CREATE TABLE cache_thread (
user_id integer NOT NULL,
mailbox varchar(255) NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
data text NOT NULL,
PRIMARY KEY (user_id, mailbox)
);
CREATE INDEX ix_cache_thread_changed ON cache_thread (changed);
CREATE TABLE cache_messages (
user_id integer NOT NULL,
mailbox varchar(255) NOT NULL,
uid integer NOT NULL,
changed datetime NOT NULL default '0000-00-00 00:00:00',
data text NOT NULL,
seen smallint NOT NULL DEFAULT '0',
deleted smallint NOT NULL DEFAULT '0',
answered smallint NOT NULL DEFAULT '0',
forwarded smallint NOT NULL DEFAULT '0',
flagged smallint NOT NULL DEFAULT '0',
mdnsent smallint NOT NULL DEFAULT '0',
PRIMARY KEY (user_id, mailbox, uid)
);
CREATE INDEX ix_cache_messages_changed ON cache_messages (changed);

@ -169,11 +169,17 @@ function rcmail_cache_gc()
// get target timestamp
$ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
$db->query("DELETE FROM ".get_table_name('messages')."
WHERE created < " . $db->fromunixtime($ts));
$db->query("DELETE FROM ".get_table_name('cache_messages')
." WHERE changed < " . $db->fromunixtime($ts));
$db->query("DELETE FROM ".get_table_name('cache')."
WHERE created < " . $db->fromunixtime($ts));
$db->query("DELETE FROM ".get_table_name('cache_index')
." WHERE changed < " . $db->fromunixtime($ts));
$db->query("DELETE FROM ".get_table_name('cache_thread')
." WHERE changed < " . $db->fromunixtime($ts));
$db->query("DELETE FROM ".get_table_name('cache')
." WHERE created < " . $db->fromunixtime($ts));
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,907 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_imap_cache.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Caching of IMAP folder contents (messages and index) |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* Interface class for accessing Roundcube messages cache
*
* @package Cache
* @author Thomas Bruederli <roundcube@gmail.com>
* @author Aleksander Machniak <alec@alec.pl>
* @version 1.0
*/
class rcube_imap_cache
{
/**
* Instance of rcube_imap
*
* @var rcube_imap
*/
private $imap;
/**
* Instance of rcube_mdb2
*
* @var rcube_mdb2
*/
private $db;
/**
* User ID
*
* @var int
*/
private $userid;
/**
* Internal (in-memory) cache
*
* @var array
*/
private $icache = array();
private $skip_deleted = false;
public $flag_fields = array('seen', 'deleted', 'answered', 'forwarded', 'flagged', 'mdnsent');
/**
* Object constructor.
*/
function __construct($db, $imap, $userid, $skip_deleted)
{
$this->db = $db;
$this->imap = $imap;
$this->userid = (int)$userid;
$this->skip_deleted = $skip_deleted;
}
/**
* Cleanup actions (on shutdown).
*/
public function close()
{
$this->save_icache();
$this->icache = null;
}
/**
* Return (sorted) messages index.
* If index doesn't exist or is invalid, will be updated.
*
* @param string $mailbox Folder name
* @param string $sort_field Sorting column
* @param string $sort_order Sorting order (ASC|DESC)
* @param bool $exiting Skip index initialization if it doesn't exist in DB
*
* @return array Messages index
*/
function get_index($mailbox, $sort_field = null, $sort_order = null, $existing = false)
{
if (empty($this->icache[$mailbox]))
$this->icache[$mailbox] = array();
$sort_order = strtoupper($sort_order) == 'ASC' ? 'ASC' : 'DESC';
// Seek in internal cache
if (array_key_exists('index', $this->icache[$mailbox])
&& ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field)
) {
if ($this->icache[$mailbox]['index']['sort_order'] == $sort_order)
return $this->icache[$mailbox]['index']['result'];
else
return array_reverse($this->icache[$mailbox]['index']['result'], true);
}
// Get index from DB (if DB wasn't already queried)
if (empty($this->icache[$mailbox]['index_queried'])) {
$index = $this->get_index_row($mailbox);
// set the flag that DB was already queried for index
// this way we'll be able to skip one SELECT, when
// get_index() is called more than once
$this->icache[$mailbox]['index_queried'] = true;
}
$data = null;
// @TODO: Think about skipping validation checks.
// If we could check only every 10 minutes, we would be able to skip
// expensive checks, mailbox selection or even IMAP connection, this would require
// additional logic to force cache invalidation in some cases
// and many rcube_imap changes to connect when needed
// Entry exist, check cache status
if (!empty($index)) {
$exists = true;
if ($sort_field == 'ANY') {
$sort_field = $index['sort_field'];
}
if ($sort_field != $index['sort_field']) {
$is_valid = false;
}
else {
$is_valid = $this->validate($mailbox, $index, $exists);
}
if ($is_valid) {
// build index, assign sequence IDs to unique IDs
$data = array_combine($index['seq'], $index['uid']);
// revert the order if needed
if ($index['sort_order'] != $sort_order)
$data = array_reverse($data, true);
}
}
else {
// Got it in internal cache, so the row already exist
$exists = array_key_exists('index', $this->icache[$mailbox]);
if ($existing) {
return null;
}
else if ($sort_field == 'ANY') {
$sort_field = '';
}
}
// Index not found, not valid or sort field changed, get index from IMAP server
if ($data === null) {
// Get mailbox data (UIDVALIDITY, counters, etc.) for status check
$mbox_data = $this->imap->mailbox_data($mailbox);
$data = array();
// Prevent infinite loop.
// It happens when rcube_imap::message_index_direct() is called.
// There id2uid() is called which will again call get_index() and so on.
if (!$sort_field && !$this->skip_deleted)
$this->icache['pending_index_update'] = true;
if ($mbox_data['EXISTS']) {
// fetch sorted sequence numbers
$data_seq = $this->imap->message_index_direct($mailbox, $sort_field, $sort_order);
// fetch UIDs
if (!empty($data_seq)) {
// Seek in internal cache
if (array_key_exists('index', (array)$this->icache[$mailbox]))
$data_uid = $this->icache[$mailbox]['index']['result'];
else
$data_uid = $this->imap->conn->fetchUIDs($mailbox, $data_seq);
// build index
if (!empty($data_uid)) {
foreach ($data_seq as $seq)
if ($uid = $data_uid[$seq])
$data[$seq] = $uid;
}
}
}
// Reset internal flags
$this->icache['pending_index_update'] = false;
// insert/update
$this->add_index_row($mailbox, $sort_field, $sort_order, $data, $mbox_data, $exists);
}
$this->icache[$mailbox]['index'] = array(
'result' => $data,
'sort_field' => $sort_field,
'sort_order' => $sort_order,
);
return $data;
}
/**
* Return messages thread.
* If threaded index doesn't exist or is invalid, will be updated.
*
* @param string $mailbox Folder name
* @param string $sort_field Sorting column
* @param string $sort_order Sorting order (ASC|DESC)
*
* @return array Messages threaded index
*/
function get_thread($mailbox)
{
if (empty($this->icache[$mailbox]))
$this->icache[$mailbox] = array();
// Seek in internal cache
if (array_key_exists('thread', $this->icache[$mailbox])) {
return array(
$this->icache[$mailbox]['thread']['tree'],
$this->icache[$mailbox]['thread']['depth'],
$this->icache[$mailbox]['thread']['children'],
);
}
// Get index from DB
$index = $this->get_thread_row($mailbox);
$data = null;
// Entry exist, check cache status
if (!empty($index)) {
$exists = true;
$is_valid = $this->validate($mailbox, $index, $exists);
if (!$is_valid) {
$index = null;
}
}
// Index not found or not valid, get index from IMAP server
if ($index === null) {
// Get mailbox data (UIDVALIDITY, counters, etc.) for status check
$mbox_data = $this->imap->mailbox_data($mailbox);
if ($mbox_data['EXISTS']) {
// get all threads (default sort order)
list ($thread_tree, $msg_depth, $has_children) = $this->imap->fetch_threads($mailbox, true);
}
$index = array(
'tree' => !empty($thread_tree) ? $thread_tree : array(),
'depth' => !empty($msg_depth) ? $msg_depth : array(),
'children' => !empty($has_children) ? $has_children : array(),
);
// insert/update
$this->add_thread_row($mailbox, $index, $mbox_data, $exists);
}
$this->icache[$mailbox]['thread'] = $index;
return array($index['tree'], $index['depth'], $index['children']);
}
/**
* Returns list of messages (headers). See rcube_imap::fetch_headers().
*
* @param string $mailbox Folder name
* @param array $msgs Message sequence numbers
* @param bool $is_uid True if $msgs contains message UIDs
*
* @return array The list of messages (rcube_mail_header) indexed by UID
*/
function get_messages($mailbox, $msgs = array(), $is_uid = true)
{
if (empty($msgs)) {
return array();
}
// Convert IDs to UIDs
// @TODO: it would be nice if we could work with UIDs only
// then, e.g. when fetching search result, index would be not needed
if (!$is_uid) {
$index = $this->get_index($mailbox, 'ANY');
foreach ($msgs as $idx => $msgid)
if ($uid = $index[$msgid])
$msgs[$idx] = $uid;
}
$flag_fields = implode(', ', array_map(array($this->db, 'quoteIdentifier'), $this->flag_fields));
// Fetch messages from cache
$sql_result = $this->db->query(
"SELECT uid, data, ".$flag_fields
." FROM ".get_table_name('cache_messages')
." WHERE user_id = ?"
." AND mailbox = ?"
." AND uid IN (".$this->db->array2list($msgs, 'integer').")",
$this->userid, $mailbox);
$msgs = array_flip($msgs);
$result = array();
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$uid = intval($sql_arr['uid']);
$result[$uid] = $this->build_message($sql_arr);
// save memory, we don't need a body here
$result[$uid]->body = null;
//@TODO: update message ID according to index data?
if (!empty($result[$uid])) {
unset($msgs[$uid]);
}
}
// Fetch not found messages from IMAP server
if (!empty($msgs)) {
$messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), true, true);
// Insert to DB and add to result list
if (!empty($messages)) {
foreach ($messages as $msg) {
$this->add_message($mailbox, $msg, !array_key_exists($msg->uid, $result));
$result[$msg->uid] = $msg;
}
}
}
return $result;
}
/**
* Returns message data.
*
* @param string $mailbox Folder name
* @param int $uid Message UID
*
* @return rcube_mail_header Message data
*/
function get_message($mailbox, $uid)
{
// Check internal cache
if (($message = $this->icache['message'])
&& $message['mailbox'] == $mailbox && $message['object']->uid == $uid
) {
return $this->icache['message']['object'];
}
$flag_fields = implode(', ', array_map(array($this->db, 'quoteIdentifier'), $this->flag_fields));
$sql_result = $this->db->query(
"SELECT data, ".$flag_fields
." FROM ".get_table_name('cache_messages')
." WHERE user_id = ?"
." AND mailbox = ?"
." AND uid = ?",
$this->userid, $mailbox, (int)$uid);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$message = $this->build_message($sql_arr);
$found = true;
//@TODO: update message ID according to index data?
}
// Get the message from IMAP server
if (empty($message)) {
$message = $this->imap->get_headers($uid, $mailbox, true);
// cache will be updated in close(), see below
}
// Save the message in internal cache, will be written to DB in close()
// Common scenario: user opens unseen message
// - get message (SELECT)
// - set message headers/structure (INSERT or UPDATE)
// - set \Seen flag (UPDATE)
// This way we can skip one UPDATE
if (!empty($message)) {
// Save current message from internal cache
$this->save_icache();
$this->icache['message'] = array(
'object' => $message,
'mailbox' => $mailbox,
'exists' => $found,
'md5sum' => md5(serialize($message)),
);
}
return $message;
}
/**
* Saves the message in cache.
*
* @param string $mailbox Folder name
* @param rcube_mail_header $message Message data
* @param bool $force Skips message in-cache existance check
*/
function add_message($mailbox, $message, $force = false)
{
if (!is_object($message) || empty($message->uid))
return;
$msg = serialize($this->db->encode(clone $message));
$flag_fields = array_map(array($this->db, 'quoteIdentifier'), $this->flag_fields);
$flag_values = array();
foreach ($this->flag_fields as $flag)
$flag_values[] = (int) $message->$flag;
// update cache record (even if it exists, the update
// here will work as select, assume row exist if affected_rows=0)
if (!$force) {
foreach ($flag_fields as $key => $val)
$flag_data[] = $val . " = " . $flag_values[$key];
$res = $this->db->query(
"UPDATE ".get_table_name('cache_messages')
." SET data = ?, changed = ".$this->db->now()
.", " . implode(', ', $flag_data)
." WHERE user_id = ?"
." AND mailbox = ?"
." AND uid = ?",
$msg, $this->userid, $mailbox, (int) $message->uid);
if ($this->db->affected_rows())
return;
}
// insert new record
$this->db->query(
"INSERT INTO ".get_table_name('cache_messages')
." (user_id, mailbox, uid, changed, data, " . implode(', ', $flag_fields) . ")"
." VALUES (?, ?, ?, ".$this->db->now().", ?, " . implode(', ', $flag_values) . ")",
$this->userid, $mailbox, (int) $message->uid, $msg);
}
/**
* Sets the flag for specified message.
*
* @param string $mailbox Folder name
* @param array $uids Message UIDs or null to change flag
* of all messages in a folder
* @param string $flag The name of the flag
* @param bool $enabled Flag state
*/
function change_flag($mailbox, $uids, $flag, $enabled = false)
{
$flag = strtolower($flag);
if (in_array($flag, $this->flag_fields)) {
// Internal cache update
if ($uids && count($uids) == 1 && ($uid = current($uids))
&& ($message = $this->icache['message'])
&& $message['mailbox'] == $mailbox && $message['object']->uid == $uid
) {
$message['object']->$flag = $enabled;
return;
}
$this->db->query(
"UPDATE ".get_table_name('cache_messages')
." SET changed = ".$this->db->now()
.", " .$this->db->quoteIdentifier($flag) . " = " . intval($enabled)
." WHERE user_id = ?"
." AND mailbox = ?"
.($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
$this->userid, $mailbox);
}
else {
// @TODO: SELECT+UPDATE?
$this->remove_message($mailbox, $uids);
}
}
/**
* Removes message(s) from cache.
*
* @param string $mailbox Folder name
* @param array $uids Message UIDs, NULL removes all messages
*/
function remove_message($mailbox = null, $uids = null)
{
if (!strlen($mailbox)) {
$this->db->query(
"DELETE FROM ".get_table_name('cache_messages')
." WHERE user_id = ?",
$this->userid);
}
else {
// Remove the message from internal cache
if (!empty($uids) && !is_array($uids) && ($message = $this->icache['message'])
&& $message['mailbox'] == $mailbox && $message['object']->uid == $uids
) {
$this->icache['message'] = null;
}
$this->db->query(
"DELETE FROM ".get_table_name('cache_messages')
." WHERE user_id = ?"
." AND mailbox = ".$this->db->quote($mailbox)
.($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
$this->userid);
}
}
/**
* Clears index cache.
*
* @param string $mailbox Folder name
*/
function remove_index($mailbox = null)
{
$this->db->query(
"DELETE FROM ".get_table_name('cache_index')
." WHERE user_id = ".intval($this->userid)
.(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
);
if (strlen($mailbox))
unset($this->icache[$mailbox]['index']);
else
$this->icache = array();
}
/**
* Clears thread cache.
*
* @param string $mailbox Folder name
*/
function remove_thread($mailbox = null)
{
$this->db->query(
"DELETE FROM ".get_table_name('cache_thread')
." WHERE user_id = ".intval($this->userid)
.(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
);
if (strlen($mailbox))
unset($this->icache[$mailbox]['thread']);
else
$this->icache = array();
}
/**
* Clears the cache.
*
* @param string $mailbox Folder name
* @param array $uids Message UIDs, NULL removes all messages in a folder
*/
function clear($mailbox = null, $uids = null)
{
$this->remove_index($mailbox);
$this->remove_thread($mailbox);
$this->remove_message($mailbox, $uids);
}
/**
* @param string $mailbox Folder name
* @param int $id Message (sequence) ID
*
* @return int Message UID
*/
function id2uid($mailbox, $id)
{
if (!empty($this->icache['pending_index_update']))
return null;
// get index if it exists
$index = $this->get_index($mailbox, 'ANY', null, true);
return $index[$id];
}
/**
* @param string $mailbox Folder name
* @param int $uid Message UID
*
* @return int Message (sequence) ID
*/
function uid2id($mailbox, $uid)
{
if (!empty($this->icache['pending_index_update']))
return null;
// get index if it exists
$index = $this->get_index($mailbox, 'ANY', null, true);
return array_search($uid, (array)$index);
}
/**
* Fetches index data from database
*/
private function get_index_row($mailbox)
{
// Get index from DB
$sql_result = $this->db->query(
"SELECT data"
." FROM ".get_table_name('cache_index')
." WHERE user_id = ?"
." AND mailbox = ?",
$this->userid, $mailbox);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$data = explode('@', $sql_arr['data']);
return array(
'seq' => explode(',', $data[0]),
'uid' => explode(',', $data[1]),
'sort_field' => $data[2],
'sort_order' => $data[3],
'deleted' => $data[4],
'validity' => $data[5],
'uidnext' => $data[6],
);
}
return null;
}
/**
* Fetches thread data from database
*/
private function get_thread_row($mailbox)
{
// Get thread from DB
$sql_result = $this->db->query(
"SELECT data"
." FROM ".get_table_name('cache_thread')
." WHERE user_id = ?"
." AND mailbox = ?",
$this->userid, $mailbox);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$data = explode('@', $sql_arr['data']);
$data[0] = unserialize($data[0]);
// build 'depth' and 'children' arrays
$depth = $children = array();
$this->build_thread_data($data[0], $depth, $children);
return array(
'tree' => $data[0],
'depth' => $depth,
'children' => $children,
'deleted' => $data[1],
'validity' => $data[2],
'uidnext' => $data[3],
);
}
return null;
}
/**
* Saves index data into database
*/
private function add_index_row($mailbox, $sort_field, $sort_order,
$data = array(), $mbox_data = array(), $exists = false)
{
$data = array(
implode(',', array_keys($data)),
implode(',', array_values($data)),
$sort_field,
$sort_order,
(int) $this->skip_deleted,
(int) $mbox_data['UIDVALIDITY'],
(int) $mbox_data['UIDNEXT'],
);
$data = implode('@', $data);
if ($exists)
$sql_result = $this->db->query(
"UPDATE ".get_table_name('cache_index')
." SET data = ?, changed = ".$this->db->now()
." WHERE user_id = ?"
." AND mailbox = ?",
$data, $this->userid, $mailbox);
else
$sql_result = $this->db->query(
"INSERT INTO ".get_table_name('cache_index')
." (user_id, mailbox, data, changed)"
." VALUES (?, ?, ?, ".$this->db->now().")",
$this->userid, $mailbox, $data);
}
/**
* Saves thread data into database
*/
private function add_thread_row($mailbox, $data = array(), $mbox_data = array(), $exists = false)
{
$data = array(
serialize($data['tree']),
(int) $this->skip_deleted,
(int) $mbox_data['UIDVALIDITY'],
(int) $mbox_data['UIDNEXT'],
);
$data = implode('@', $data);
if ($exists)
$sql_result = $this->db->query(
"UPDATE ".get_table_name('cache_thread')
." SET data = ?, changed = ".$this->db->now()
." WHERE user_id = ?"
." AND mailbox = ?",
$data, $this->userid, $mailbox);
else
$sql_result = $this->db->query(
"INSERT INTO ".get_table_name('cache_thread')
." (user_id, mailbox, data, changed)"
." VALUES (?, ?, ?, ".$this->db->now().")",
$this->userid, $mailbox, $data);
}
/**
* Checks index/thread validity
*/
private function validate($mailbox, $index, &$exists = true)
{
$is_thread = isset($index['tree']);
// Get mailbox data (UIDVALIDITY, counters, etc.) for status check
$mbox_data = $this->imap->mailbox_data($mailbox);
// @TODO: Think about skipping validation checks.
// If we could check only every 10 minutes, we would be able to skip
// expensive checks, mailbox selection or even IMAP connection, this would require
// additional logic to force cache invalidation in some cases
// and many rcube_imap changes to connect when needed
// Check UIDVALIDITY
// @TODO: while we're storing message sequence numbers in thread
// index, should UIDVALIDITY invalidate the thread data?
if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
// the whole cache (all folders) is invalid
$this->clear();
$exists = false;
return false;
}
// Folder is empty but cache isn't
if (empty($mbox_data['EXISTS']) && (!empty($index['seq']) || !empty($index['tree']))) {
$this->clear($mailbox);
$exists = false;
return false;
}
// Check UIDNEXT
if ($index['uidnext'] != $mbox_data['UIDNEXT']) {
unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']);
return false;
}
// Index was created with different skip_deleted setting
if ($this->skip_deleted != $index['deleted']) {
return false;
}
// @TODO: find better validity check for threaded index
if ($is_thread) {
// check messages number...
if ($mbox_data['EXISTS'] != max(array_keys($index['depth']))) {
return false;
}
return true;
}
// The rest of checks, more expensive
if (!empty($this->skip_deleted)) {
// compare counts if available
if ($mbox_data['COUNT_UNDELETED'] != null
&& $mbox_data['COUNT_UNDELETED'] != count($index['uid'])) {
return false;
}
// compare UID sets
if ($mbox_data['ALL_UNDELETED'] != null) {
$uids_new = rcube_imap_generic::uncompressMessageSet($mbox_data['ALL_UNDELETED']);
$uids_old = $index['uid'];
if (count($uids_new) != count($uids_old)) {
return false;
}
sort($uids_new, SORT_NUMERIC);
sort($uids_old, SORT_NUMERIC);
if ($uids_old != $uids_new)
return false;
}
else {
// get all undeleted messages excluding cached UIDs
$ids = $this->imap->search_once($mailbox, 'ALL UNDELETED NOT UID '.
rcube_imap_generic::compressMessageSet($index['uid']));
if (!empty($ids)) {
$index = null; // cache invalid
}
}
}
else {
// check messages number...
if ($mbox_data['EXISTS'] != max($index['seq'])) {
return false;
}
// ... and max UID
if (max($index['uid']) != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox, true)) {
return false;
}
}
return true;
}
/**
* Converts cache row into message object.
*
* @param array $sql_arr Message row data
*
* @return rcube_mail_header Message object
*/
private function build_message($sql_arr)
{
$message = $this->db->decode(unserialize($sql_arr['data']));
if ($message) {
foreach ($this->flag_fields as $field)
$message->$field = (bool) $sql_arr[$field];
}
return $message;
}
/**
* Creates 'depth' and 'children' arrays from stored thread 'tree' data.
*/
private function build_thread_data($data, &$depth, &$children, $level = 0)
{
foreach ((array)$data as $key => $val) {
$children[$key] = !empty($val);
$depth[$key] = $level;
if (!empty($val))
$this->build_thread_data($val, $depth, $children, $level + 1);
}
}
/**
* Saves message stored in internal cache
*/
private function save_icache()
{
// Save current message from internal cache
if ($message = $this->icache['message']) {
$object = $message['object'];
// remove body too big (>500kB)
if ($object->body && strlen($object->body) > 500 * 1024)
$object->body = null;
// calculate current md5 sum
$md5sum = md5(serialize($object));
if ($message['md5sum'] != $md5sum) {
$this->add_message($message['mailbox'], $object, !$message['exists']);
}
$this->icache['message']['md5sum'] = $md5sum;
}
}
}

File diff suppressed because it is too large Load Diff

@ -77,7 +77,7 @@ class rcube_message
$this->imap->get_all_headers = true;
$this->uid = $uid;
$this->headers = $this->imap->get_headers($uid, NULL, true, true);
$this->headers = $this->imap->get_message($uid);
if (!$this->headers)
return;
@ -94,9 +94,9 @@ class rcube_message
'_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid))
);
if ($this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) {
$this->get_mime_numbers($this->structure);
$this->parse_structure($this->structure);
if (!empty($this->headers->structure)) {
$this->get_mime_numbers($this->headers->structure);
$this->parse_structure($this->headers->structure);
}
else {
$this->body = $this->imap->get_body($uid);

@ -1,77 +1,7 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_mime_struct.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide functions for handling mime messages structure |
| |
| Based on Iloha MIME Library. See http://ilohamail.org/ for details |
| |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
| Author: Ryo Chijiiwa <Ryo@IlohaMail.org> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* Helper class to process IMAP's BODYSTRUCTURE string
*
* @package Mail
* @author Aleksander Machniak <alec@alec.pl>
*/
class rcube_mime_struct
{
private $structure;
function __construct($str=null)
{
if ($str)
$this->structure = $this->parseStructure($str);
}
/*
* Parses IMAP's BODYSTRUCTURE string into array
*/
function parseStructure($str)
{
$line = substr($str, 1, strlen($str) - 2);
$line = str_replace(')(', ') (', $line);
$struct = rcube_imap_generic::tokenizeResponse($line);
if (!is_array($struct[0]) && (strcasecmp($struct[0], 'message') == 0)
&& (strcasecmp($struct[1], 'rfc822') == 0)) {
$struct = array($struct);
}
return $struct;
}
/*
* Parses IMAP's BODYSTRUCTURE string into array and loads it into class internal variable
*/
function loadStructure($str)
function getStructurePartType($structure, $part)
{
if (empty($str))
return true;
$this->structure = $this->parseStructure($str);
return (!empty($this->structure));
}
function getPartType($part)
{
$part_a = $this->getPartArray($this->structure, $part);
$part_a = self::getPartArray($structure, $part);
if (!empty($part_a)) {
if (is_array($part_a[0]))
return 'multipart';
@ -82,9 +12,9 @@ class rcube_mime_struct
return 'other';
}
function getPartEncoding($part)
function getStructurePartEncoding($structure, $part)
{
$part_a = $this->getPartArray($this->structure, $part);
$part_a = self::getPartArray($structure, $part);
if ($part_a) {
if (!is_array($part_a[0]))
return $part_a[5];
@ -93,9 +23,9 @@ class rcube_mime_struct
return '';
}
function getPartCharset($part)
function getStructurePartCharset($structure, $part)
{
$part_a = $this->getPartArray($this->structure, $part);
$part_a = self::getPartArray($structure, $part);
if ($part_a) {
if (is_array($part_a[0]))
return '';
@ -112,7 +42,7 @@ class rcube_mime_struct
return '';
}
function getPartArray($a, $part)
function getStructurePartArray($a, $part)
{
if (!is_array($a)) {
return false;
@ -137,9 +67,7 @@ class rcube_mime_struct
else
return $a;
}
else if (($part==0) || (empty($part))) {
else if (($part == 0) || (empty($part))) {
return $a;
}
}
}

@ -1454,7 +1454,7 @@ function rcmail_send_mdn($message, &$smtp_error)
if (!is_object($message) || !is_a($message, rcube_message))
$message = new rcube_message($message);
if ($message->headers->mdn_to && !$message->headers->mdn_sent &&
if ($message->headers->mdn_to && !$message->headers->mdnsent &&
($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*')))
{
$identity = $RCMAIL->user->get_identity();

@ -77,7 +77,7 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) {
// check for unset disposition notification
if ($MESSAGE->headers->mdn_to &&
!$MESSAGE->headers->mdn_sent && !$MESSAGE->headers->seen &&
!$MESSAGE->headers->mdnsent && !$MESSAGE->headers->seen &&
($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*')) &&
$mbox_name != $CONFIG['drafts_mbox'] &&
$mbox_name != $CONFIG['sent_mbox'])

Loading…
Cancel
Save