source: bookmarks/trunk/BookmarkApp.pm @ 19

Last change on this file since 19 was 19, checked in by peter, 11 years ago

added support for viewing the list of bookmarks as XBEL

File size: 7.3 KB
Line 
1package BookmarkApp;
2use strict;
3use warnings;
4use base qw{CGI::Application};
5
6use CGI::Application::Plugin::TT;
7
8use Encode;
9use HTTP::Date qw{time2isoz};
10use JSON;
11use Bookmarks;
12
13use URI;
14my $base_uri = URI->new;
15$base_uri->scheme('http');
16$base_uri->host($ENV{HTTP_X_FORWARDED_HOST} || $ENV{SERVER_NAME});
17$base_uri->port($ENV{SERVER_PORT});
18$base_uri->path($ENV{SCRIPT_NAME} . '/');
19
20my $dbname = 'fk.db';
21my $bookmarks = Bookmarks->new({
22    dbname   => $dbname,
23    base_uri => $base_uri->canonical,
24});
25
26sub setup {
27    my $self = shift;
28    $self->mode_param(path_info => 1);
29    $self->run_modes([qw{
30        list
31        feed
32        view
33        edit
34    }]);
35}
36
37sub list {
38    my $self = shift;
39    my $q = $self->query;
40
41    # check for a uri param, and if there is one present,
42    # see if a bookmark for that URI already exists
43    if (defined(my $uri = $q->param('uri'))) {
44        my $bookmark = $bookmarks->get_bookmark({ uri => $uri });
45        if ($bookmark) {
46            # redirect to the view of the existing bookmark
47            $self->header_type('redirect');
48            $self->header_props(
49                -uri => $q->url . '/' . $bookmark->{id},
50            );
51            return;
52        } else {
53            # bookmark was not found; show the form to create a new bookmark
54            $bookmark->{uri} = $uri;
55            $bookmark->{title} = $q->param('title');
56            $self->header_props(
57                -type    => 'text/html',
58                -charset => 'UTF-8',
59                -status  => 404,
60            );
61            return $self->tt_process(
62                'bookmark.tt',
63                $bookmark,
64            );
65        }
66    }
67
68    # list all the bookmarks
69    my $format = $q->param('format') || 'html';
70    my $tag = $q->param('tag');
71    my $limit = $q->param('limit');
72    my $offset = $q->param('offset');
73    my @resources = $bookmarks->get_resources({
74        tag    => $tag,
75        limit  => $limit,
76        offset => $offset,
77    });
78    my @all_tags = $bookmarks->get_tags({ selected => $tag });
79    my @cotags = $bookmarks->get_cotags({ tag => $tag });
80
81    if ($format eq 'json') {
82        $self->header_props(
83            -type    => 'application/json',
84            -charset => 'UTF-8',
85        );
86        return decode_utf8(
87            encode_json({
88                bookmarks => \@resources,
89            })
90        );
91    } elsif ($format eq 'xbel') {
92        require XML::XBEL;
93        #TODO: conditional support; if XML::XBEL is not present, return a 5xx response
94
95        my $xbel = XML::XBEL->new;
96
97        $xbel->new_document({
98            title => 'Bookmarks' . ($tag ? " tagged as $tag" : ''),
99        });
100
101        for my $bookmark (@resources) {
102            my $cdatetime = time2isoz $bookmark->{ctime};
103            my $mdatetime = time2isoz $bookmark->{mtime};
104            # make the timestamps W3C-correct
105            s/ /T/ foreach ($cdatetime, $mdatetime);
106
107            $xbel->add_bookmark({
108                href     => $bookmark->{uri},
109                title    => $bookmark->{title},
110                desc     => 'Tags: ' . join(', ', @{ $bookmark->{tags} }),
111                added    => $cdatetime,
112                #XXX: are we sure that modified is the mtime of the bookmark or the resource?
113                modified => $mdatetime,
114            });
115        }
116
117        $self->header_props(
118            -type    => 'application/xml',
119            -charset => 'UTF-8',
120        );
121
122        return $xbel->toString;
123    } else {
124        $self->header_props(
125            -type    => 'text/html',
126            -charset => 'UTF-8',
127        );
128
129        # set the base URL, adding a trailing slash if needed
130        my $base_url = $q->url;
131        $base_url .= '/' if $base_url =~ m{/bookmarks$};
132
133        return $self->tt_process(
134            'list.tt',
135            {
136                base_url     => $base_url,
137                selected_tag => $tag,
138                tags         => \@all_tags,
139                cotags       => \@cotags,
140                resources    => \@resources,
141            },
142        );
143    }
144}
145
146sub feed {
147    my $self = shift;
148    my $q = $self->query;
149
150    my $tag = $q->param('tag');
151
152    require XML::Atom::Feed;
153    require XML::Atom::Entry;
154    require XML::Atom::Link;
155
156    my $feed = XML::Atom::Feed->new;
157    $feed->title('Bookmarks' . ($tag ? " tagged as $tag" : ''));
158    $feed->id($base_uri->canonical . 'feed');
159
160    # construct a feed from the most recent 12 bookmarks
161    for my $bookmark ($bookmarks->get_resources({ tag => $tag, limit => 12 })) {
162        my $entry = XML::Atom::Entry->new;
163        $entry->id($bookmark->{bookmark_uri});
164        $entry->title($bookmark->{title});
165        my $link = XML::Atom::Link->new;
166        $link->href($bookmark->{uri});
167        $entry->add_link($link);
168        $entry->summary('Tags: ' . join(', ', @{ $bookmark->{tags} }));
169        $feed->add_entry($entry);
170    }
171
172    $self->header_props(
173        -type => 'application/atom+xml',
174        -charset => 'UTF-8',
175    );
176    return $feed->as_xml;
177}
178
179sub view {
180    my $self = shift;
181    my $id = $self->param('id');
182    my $field = $self->param('field');
183    my $format = $self->query->param('format') || 'html';
184
185    my $bookmark = $bookmarks->get_bookmark({ id => $id });
186    if ($bookmark) {
187        if ($field) {
188            # respond with just the requested field as plain text
189            $self->header_props(
190                -type    => 'text/plain',
191                -charset => 'UTF-8',
192            );
193            my $value = $bookmark->{$field};
194            return ref $value eq 'ARRAY' ? join(',', @{ $value }) : $value;
195        } else {
196            if ($format eq 'json') {
197                $self->header_props(
198                    -type    => 'application/json',
199                    -charset => 'UTF-8',
200                );
201                return decode_utf8(encode_json($bookmark));
202            } else {
203                # display the bookmark form for this bookmark
204                $bookmark->{exists} = 1;
205                $bookmark->{created} = "Created " . localtime($bookmark->{ctime});
206                $bookmark->{created} .= '; Updated ' . localtime($bookmark->{mtime}) unless $bookmark->{ctime} == $bookmark->{mtime};
207                $self->header_props(
208                    -type    => 'text/html',
209                    -charset => 'UTF-8',
210                );
211                return $self->tt_process(
212                    'bookmark.tt',
213                    $bookmark,
214                );
215            }
216        }
217    } else {
218        $self->header_props(
219            -type    => 'text/html',
220            -charset => 'UTF-8',
221            -status  => 404,
222        );
223        return "Bookmark $id Not Found";
224    }
225}
226
227#TODO: split this into edit and create methods
228sub edit {
229    my $self = shift;
230    my $q = $self->query;
231    #TODO: get the bookmark based on the id and edit it directly?
232    #TODO: deal with changing URIs
233    my $uri = $q->param('uri');
234    my $title = $q->param('title');
235    my @tags = split ' ', $q->param('tags');
236    $bookmarks->add({
237        uri   => $uri,
238        title => $title,
239        tags  => \@tags,
240    });
241
242=begin
243
244    my $location = URI->new($q->url);
245    $location->query_form(uri => $uri) if defined $q->url_param('uri');
246    $location->fragment('updated');
247
248=cut
249
250    # return to the form
251    $self->header_type('redirect');
252    $self->header_props(
253        -uri => $ENV{REQUEST_URI},
254        -status => 303,
255    );
256}
257
2581;
Note: See TracBrowser for help on using the repository browser.