]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/IMAPTracker.pm
net_reader: restart on first UID when UIDVALIDITY changes
[public-inbox.git] / lib / PublicInbox / IMAPTracker.pm
1 # Copyright (C) 2018-2021 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 package PublicInbox::IMAPTracker;
4 use strict;
5 use parent qw(PublicInbox::Lock);
6 use DBI;
7 use DBD::SQLite;
8 use PublicInbox::Config;
9
10 sub create_tables ($) {
11         my ($dbh) = @_;
12
13         $dbh->do(<<'');
14 CREATE TABLE IF NOT EXISTS imap_last (
15         url VARCHAR PRIMARY KEY NOT NULL,
16         uid_validity INTEGER NOT NULL,
17         uid INTEGER NOT NULL,
18         UNIQUE (url)
19 )
20
21 }
22
23 sub dbh_new ($) {
24         my ($dbname) = @_;
25         my $dbh = DBI->connect("dbi:SQLite:dbname=$dbname", '', '', {
26                 AutoCommit => 1,
27                 RaiseError => 1,
28                 PrintError => 0,
29                 sqlite_use_immediate_transaction => 1,
30         });
31         $dbh->{sqlite_unicode} = 1;
32
33         # TRUNCATE reduces I/O compared to the default (DELETE).
34         # Allow and preserve user-overridden WAL, but don't force it.
35         my $jm = $dbh->selectrow_array('PRAGMA journal_mode');
36         $dbh->do('PRAGMA journal_mode = TRUNCATE') if $jm ne 'wal';
37
38         create_tables($dbh);
39         $dbh;
40 }
41
42 sub get_last ($;$) {
43         my ($self, $validity) = @_;
44         my $sth;
45         if (defined $validity) {
46                 $sth = $self->{dbh}->prepare_cached(<<'', undef, 1);
47 SELECT uid_validity, uid FROM imap_last WHERE url = ? AND uid_validity = ?
48
49                 $sth->execute($self->{url}, $validity);
50         } else {
51                 $sth = $self->{dbh}->prepare_cached(<<'', undef, 1);
52 SELECT uid_validity, uid FROM imap_last WHERE url = ?
53
54                 $sth->execute($self->{url});
55         }
56         $sth->fetchrow_array;
57 }
58
59 sub update_last ($$$) {
60         my ($self, $validity, $last_uid) = @_;
61         return unless defined $last_uid;
62         my $sth = $self->{dbh}->prepare_cached(<<'');
63 INSERT OR REPLACE INTO imap_last (url, uid_validity, uid)
64 VALUES (?, ?, ?)
65
66         $self->lock_acquire;
67         my $rv = $sth->execute($self->{url}, $validity, $last_uid);
68         $self->lock_release;
69         $rv;
70 }
71
72 sub new {
73         my ($class, $url, $dbname) = @_;
74
75         unless (defined($dbname)) {
76                 # original name for compatibility with old setups:
77                 $dbname = PublicInbox::Config->config_dir() . '/imap.sqlite3';
78
79                 # use the new XDG-compliant name for new setups:
80                 if (!-f $dbname) {
81                         $dbname = ($ENV{XDG_DATA_HOME} //
82                                         (($ENV{HOME} // '/nonexistent').
83                                          '/.local/share')) .
84                                 '/public-inbox/imap.sqlite3';
85                 }
86         }
87         if (!-f $dbname) {
88                 require File::Path;
89                 require File::Basename;
90                 require PublicInbox::Spawn;
91                 File::Path::mkpath(File::Basename::dirname($dbname));
92                 open my $fh, '+>>', $dbname or die "failed to open $dbname: $!";
93                 PublicInbox::Spawn::nodatacow_fd(fileno($fh));
94         }
95         my $self = bless { lock_path => "$dbname.lock", url => $url }, $class;
96         $self->lock_acquire;
97         $self->{dbh} = dbh_new($dbname);
98         $self->lock_release;
99         $self;
100 }
101
102 1;