source: bookmarks/trunk/Bookmarks.pm @ 15

Last change on this file since 15 was 15, checked in by peter, 12 years ago
  • Bookmarks uses Moose instead of Class::Accessor
  • if not dbh is specified in the Bookmarks constructor, it can use a dbname parameter to create a SQLite DBH
  • the SQLite DBH created form dbname has the foreign_keys pragma set
  • added foreign key constraints to the bookmarks.sql table definitions for bookmarks and tags
  • added a required --file option to bkmk to specify the database file to use
  • added a load command to bkmk that loads bookmarks dumped as YAML using bkmk list
  • Bookmarks::add() can take mtime and id parameters (useful for reconstructing a database from the YAML dump of bkmk list)
  • BookmarkApp and bkmk no longer use DBI directly; just pass a dbname to the Bookmarks constructor
  • changed the default database for BookmarkApp to fk.db (schema from this revision's updated bookmarks.sql, with foreign keys)
File size: 6.8 KB
Line 
1package Bookmarks;
2
3use Moose;
4use SQL::Interp qw{:all};
5use Bookmark;
6
7has dbh      => ( is => 'rw' );
8has base_uri => ( is => 'ro' );
9
10sub BUILD {
11    my $self = shift;
12    my $args = shift;
13
14    if (!$self->dbh) {
15        if ($args->{dbname}) {
16            require DBI;
17            $self->dbh(DBI->connect("dbi:SQLite:dbname=$$args{dbname}", "", "", { RaiseError => 1, PrintError => 0 }));
18            # enable foreign key support (requires DBD::SQLite 1.26_05 or above (sqlite 3.6.19 or above))
19            $self->dbh->do('pragma foreign_keys = on;');
20        } else {
21            #TODO: figure out how to make croak play nice with Moose to get the user-visible caller line
22            die "No dbh or dbname specified in the constructor";
23        }
24    }
25}
26
27sub get_bookmark {
28    my $self = shift;
29    my $params = shift;
30    my $sth;
31    if ($params->{id}) {
32        $sth = $self->dbh->prepare('select id,resources.uri,title,ctime,mtime from bookmarks join resources on bookmarks.uri=resources.uri where id=?');
33        $sth->execute($params->{id});
34    } elsif ($params->{uri}) {
35        $sth = $self->dbh->prepare('select id,resources.uri,title,ctime,mtime from bookmarks join resources on bookmarks.uri=resources.uri where resources.uri=?');
36        $sth->execute($params->{uri});
37    } else {
38        die "Must specify either id or uri";
39    }
40    my $bookmark = $sth->fetchrow_hashref;
41    if ($bookmark) {
42        my $sth_tag = $self->dbh->prepare('select tag from tags where uri = ? order by tag');
43        $sth_tag->execute($bookmark->{uri});
44        $bookmark->{tags} = [ map { $$_[0] } @{ $sth_tag->fetchall_arrayref } ];
45        if ($self->base_uri) {
46            $bookmark->{bookmark_uri} = $self->base_uri . $bookmark->{id};
47        }
48    }
49    return $bookmark;
50}
51
52sub get_resources {
53    my $self = shift;
54    my $params = shift;
55    my $tag = $params->{tag};
56    my $limit = $params->{limit};
57    my $offset = $params->{offset};
58
59    my ($sql, @bind) = sql_interp(
60        'select * from resources join bookmarks on resources.uri = bookmarks.uri',
61        ($tag   ? ('join tags on resources.uri = tags.uri where tags.tag =', \$tag) : ''),
62        'order by ctime desc',
63        ($limit ? ('limit', \$limit) : ()),
64        # an offset is only allowed if we have a limit clause
65        ($limit && $offset ? ('offset', \$offset) : ()),
66    );
67
68    my $sth_resource = $self->dbh->prepare($sql);
69    $sth_resource->execute(@bind);
70
71    my $sth_tag = $self->dbh->prepare('select tag from tags where uri = ? order by tag');
72    my @resources;
73    while (my $resource = $sth_resource->fetchrow_hashref) {
74        $sth_tag->execute($resource->{uri});
75        $resource->{tags} = [ map { $$_[0] } @{ $sth_tag->fetchall_arrayref } ];
76        if ($self->base_uri) {
77            $resource->{bookmark_uri} = $self->base_uri . $resource->{id};
78        }
79        push @resources, $resource;
80    }
81    return @resources;
82}
83
84sub get_tags {
85    my $self = shift;
86    my $params = shift;
87    my $tag = $params->{selected};
88    my $sth_all_tags = $self->dbh->prepare('select tag, count(tag) as count, tag = ? as selected from tags group by tag order by tag');
89    $sth_all_tags->execute($tag);
90    my $all_tags = $sth_all_tags->fetchall_arrayref({});
91    return @{ $all_tags };
92}
93
94sub get_cotags {
95    my $self = shift;
96    my $params = shift;
97    my $tag = $params->{tag};
98    my $sth = $self->dbh->prepare('select tag, count(tag) as count from tags where tag != ? and uri in (select uri from tags where tag = ?) group by tag order by tag');
99    $sth->execute($tag, $tag);
100    return @{ $sth->fetchall_arrayref({}) };
101}
102
103sub add {
104    my $self = shift;
105    my $bookmark = shift;
106
107    my $uri = $bookmark->{uri};
108    my $title = $bookmark->{title};
109    #TODO: accept a ctime or mtime
110    my $ctime = $bookmark->{ctime} || time;
111    my $mtime = $bookmark->{mtime} || $ctime;
112    my $id = $bookmark->{id};
113
114    # create an entry for the resource
115    my $sth_resource = $self->dbh->prepare('insert into resources (uri, title) values (?, ?)');
116    eval {
117        $sth_resource->execute($uri, $title);
118    };
119    if ($@) {
120        if ($@ =~ /column uri is not unique/) {
121            # this is not truly an error condition; the resource is already listed
122            # update the title instead
123            my $sth_update = $self->dbh->prepare('update resources set title = ? where uri = ?');
124            $sth_update->execute($title, $uri);
125        } else {
126            die $@;
127        }
128    }
129
130    # create the bookmark
131    my ($sql_bookmark, @bind_bookmark) = sql_interp(
132        'insert into bookmarks', { ($id ? (id => $id) : ()), uri => $uri, ctime => $ctime, mtime => $mtime }
133    );
134    my $sth_bookmark = $self->dbh->prepare($sql_bookmark);
135    eval {
136        $sth_bookmark->execute(@bind_bookmark);
137    };
138    if ($@) {
139        if ($@ =~ /column uri is not unique/) {
140            # this is not truly an error condition; the bookmark was already there
141            # update the mtime instead
142            # TODO: only update mtime if the tag list is changed?
143            my $sth_update = $self->dbh->prepare('update bookmarks set mtime = ? where uri = ?');
144            $sth_update->execute($mtime, $uri);
145        } else {
146            die $@;
147        }
148    }
149
150    my %new_tags = map { $_ => 1 } @{ $bookmark->{tags} };
151    my $sth_delete_tag = $self->dbh->prepare('delete from tags where uri = ? and tag = ?');
152    my $sth_insert_tag = $self->dbh->prepare('insert into tags (uri, tag) values (?, ?)');
153    my $sth_current_tags = $self->dbh->prepare('select tag from tags where uri = ?');
154    $sth_current_tags->execute($uri);
155    while (my ($tag) = $sth_current_tags->fetchrow_array) {
156        if (!$new_tags{$tag}) {
157            # if a current tag is not in the new tags, remove it from the database
158            $sth_delete_tag->execute($uri, $tag);
159        } else {
160            # if a new tag is already in the database, remove it from the list of tags to add
161            delete $new_tags{$tag};
162        }
163    }
164    for my $tag (keys %new_tags) {
165        $sth_insert_tag->execute($uri, $tag);
166    }
167
168=begin
169
170    # clear all tags
171    my $sth_delete_tag = $self->dbh->prepare('delete from tags where uri = ?');
172    $sth_delete_tag->execute($uri);
173    my $sth_tag = $self->dbh->prepare('insert into tags (uri, tag) values (?, ?)');
174    for my $tag (@{ $bookmark->{tags} }) {
175        #print $tag, "\n";
176        # prevent duplicate (uri,tag) pairs in the database
177        # TODO: should POST with a set of tags ever remove tags?
178        eval {
179            $sth_tag->execute($uri, $tag);
180        };
181        if ($@) {
182            if ($@ =~ /columns uri, tag are not unique/) {
183                # this is not truly an error condition; the tag was already there
184            } else {
185                die $@;
186            }
187        }
188    }
189
190=cut
191
192    # return the newly created or updated bookmark
193    return $self->get_bookmark({ uri => $uri });
194}
195
196# module returns true
1971;
Note: See TracBrowser for help on using the repository browser.