about summary refs log tree commit homepage
path: root/lib/PublicInbox/IMAPTracker.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox/IMAPTracker.pm')
-rw-r--r--lib/PublicInbox/IMAPTracker.pm91
1 files changed, 91 insertions, 0 deletions
diff --git a/lib/PublicInbox/IMAPTracker.pm b/lib/PublicInbox/IMAPTracker.pm
new file mode 100644
index 00000000..4efa8a7e
--- /dev/null
+++ b/lib/PublicInbox/IMAPTracker.pm
@@ -0,0 +1,91 @@
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+package PublicInbox::IMAPTracker;
+use strict;
+use parent qw(PublicInbox::Lock);
+use DBI;
+use DBD::SQLite;
+use PublicInbox::Config;
+
+sub create_tables ($) {
+        my ($dbh) = @_;
+
+        $dbh->do(<<'');
+CREATE TABLE IF NOT EXISTS imap_last (
+        url VARCHAR PRIMARY KEY NOT NULL,
+        uid_validity INTEGER NOT NULL,
+        uid INTEGER NOT NULL,
+        UNIQUE (url)
+)
+
+}
+
+sub dbh_new ($) {
+        my ($dbname) = @_;
+        my $dbh = DBI->connect("dbi:SQLite:dbname=$dbname", '', '', {
+                AutoCommit => 1,
+                RaiseError => 1,
+                PrintError => 0,
+                sqlite_use_immediate_transaction => 1,
+        });
+        $dbh->{sqlite_unicode} = 1;
+
+        # TRUNCATE reduces I/O compared to the default (DELETE).
+        # Allow and preserve user-overridden WAL, but don't force it.
+        my $jm = $dbh->selectrow_array('PRAGMA journal_mode');
+        $dbh->do('PRAGMA journal_mode = TRUNCATE') if $jm ne 'wal';
+
+        create_tables($dbh);
+        $dbh;
+}
+
+sub get_last ($) {
+        my ($self) = @_;
+        my $sth = $self->{dbh}->prepare_cached(<<'', undef, 1);
+SELECT uid_validity, uid FROM imap_last WHERE url = ?
+
+        $sth->execute($self->{url});
+        $sth->fetchrow_array;
+}
+
+sub update_last ($$$) {
+        my ($self, $validity, $last_uid) = @_;
+        return unless defined $last_uid;
+        my $sth = $self->{dbh}->prepare_cached(<<'');
+INSERT OR REPLACE INTO imap_last (url, uid_validity, uid)
+VALUES (?, ?, ?)
+
+        $self->lock_acquire;
+        my $rv = $sth->execute($self->{url}, $validity, $last_uid);
+        $self->lock_release;
+        $rv;
+}
+
+sub new {
+        my ($class, $url) = @_;
+
+        # original name for compatibility with old setups:
+        my $dbname = PublicInbox::Config->config_dir() . '/imap.sqlite3';
+
+        # use the new XDG-compliant name for new setups:
+        if (!-f $dbname) {
+                $dbname = ($ENV{XDG_DATA_HOME} //
+                        (($ENV{HOME} // '/nonexistent').'/.local/share')) .
+                        '/public-inbox/imap.sqlite3';
+        }
+        if (!-f $dbname) {
+                require File::Path;
+                require PublicInbox::Syscall;
+                my ($dir) = ($dbname =~ m!(.*?/)[^/]+\z!);
+                File::Path::mkpath($dir);
+                PublicInbox::Syscall::nodatacow_dir($dir);
+                open my $fh, '+>>', $dbname or die "failed to open $dbname: $!";
+        }
+        my $self = bless { lock_path => "$dbname.lock", url => $url }, $class;
+        $self->lock_acquire;
+        $self->{dbh} = dbh_new($dbname);
+        $self->lock_release;
+        $self;
+}
+
+1;