source: bookmarks/trunk/lib/Bookmarks/List.pm @ 88

Last change on this file since 88 was 88, checked in by peter, 9 years ago

Better separation of the model for searching and the web app controller/representation layer

  • renamed Bookmarks::get_bookmarks() to search()
  • Bookmarks::search() now returns a Bookmarks::Search object instead of a Bookmarks::List
  • search results now reside in the Bookmarks::Search class
File size: 6.9 KB
Line 
1package Bookmarks::List;
2
3use Moose;
4
5use Encode;
6use HTTP::Date qw{time2iso time2isoz};
7
8has bookmarks => (
9    is => 'ro',
10    isa => 'Bookmarks',
11);
12has search => (
13    is => 'ro',
14    isa => 'Bookmarks::Search',
15    handles => [qw{query tags limit offset results}],
16);
17has title => (
18    is => 'ro',
19    builder => '_build_title',
20    lazy => 1,
21);
22
23sub _get_list_links {
24    my $self = shift;
25    my ($self_type, $query) = @_;
26
27    # remove extraneous blank ?q= parameters
28    delete $query->{q} if defined $query->{q} && $query->{q} eq '';
29   
30    my @links = (
31        {
32            text => 'JSON',
33            type => 'application/json',
34            query => {
35                %$query,
36                format => 'json',
37            },
38        },
39        {
40            text => 'XBEL',
41            type => 'application/xml',
42            query => {
43                %$query,
44                format => 'xbel',
45            },
46        },
47        {
48            text => 'Atom',
49            type => 'application/atom+xml',
50            path => 'feed',
51            query => {
52                %$query,
53            },
54        },
55        {
56            text => 'CSV',
57            type => 'text/csv',
58            query => {
59                %$query,
60                format => 'csv',
61            },
62        },
63        {
64            text => 'URI List',
65            type => 'text/uri-list',
66            query => {
67                %$query,
68                format => 'text',
69            },
70        },
71        {
72            text => 'HTML',
73            type => 'text/html',
74            query => {
75                %$query,
76            },
77        },
78    );
79
80    for my $link (@links) {
81        $link->{rel}  = $link->{type} eq $self_type ? 'self' : 'alternate';
82        $link->{href} = URI->new_abs($link->{path} || '', $self->bookmarks->base_uri);
83        $link->{href}->query_form($link->{query});
84    }
85
86    return @links;
87}
88
89sub _build_title {
90    my $self = shift;
91    return 'Bookmarks' . (@{ $self->tags } ? " tagged as " . join(' & ', @{ $self->tags }) : '') . ($self->query ? " matching '" . $self->query . "'" : '');
92}
93
94sub as_json {
95    my $self = shift;
96    require JSON;
97    my $json = decode_utf8(
98        JSON->new->utf8->convert_blessed->encode({
99            bookmarks => $self->results,
100        })
101    );
102    return [200, ['Content-Type' => 'application/json; charset=UTF-8'], [$json]];
103}
104
105sub as_xbel {
106    my $self = shift;
107    require XML::XBEL;
108    #TODO: conditional support; if XML::XBEL is not present, return a 5xx response
109
110    my $xbel = XML::XBEL->new;
111
112    $xbel->new_document({
113        title => $self->title,
114    });
115
116    for my $bookmark (@{ $self->results }) {
117        my $cdatetime = time2isoz $bookmark->ctime;
118        my $mdatetime = time2isoz $bookmark->mtime;
119        # make the timestamps W3C-correct
120        s/ /T/ foreach ($cdatetime, $mdatetime);
121
122        $xbel->add_bookmark({
123            href     => $bookmark->uri,
124            title    => $bookmark->title,
125            desc     => 'Tags: ' . join(', ', @{ $bookmark->tags }),
126            added    => $cdatetime,
127            #XXX: are we sure that modified is the mtime of the bookmark or the resource?
128            modified => $mdatetime,
129        });
130    }
131
132    return [200, ['Content-Type' => 'application/xml; charset=UTF-8'], [$xbel->toString]];
133}
134
135sub as_text {
136    my $self = shift;
137    my $text = join '', 
138        map {
139            sprintf "# %s\n# Tags: %s\n%s\n",
140            $_->title,
141            join(', ', @{ $_->tags }), 
142            $_->uri
143        } @{ $self->results };
144    return [200, ['Content-Type' => 'text/uri-list; charset=UTF-8'], [$text]];
145}
146
147sub as_csv {
148    my $self = shift;
149    require Text::CSV::Encoded;
150    my $csv = Text::CSV::Encoded->new({ encoding_out => 'utf8' });
151    my $text = qq{id,uri,title,tags,ctime,mtime\n};
152    for my $bookmark (@{ $self->results }) {
153        my $success = $csv->combine(
154            $bookmark->id,
155            $bookmark->uri,
156            $bookmark->title,
157            join(' ', @{ $bookmark->tags }),
158            $bookmark->ctime,
159            $bookmark->mtime,
160        );
161        $text .= $csv->string . "\n" if $success;
162    }
163
164    # include the local timestamp in the attchment filename
165    my $dt = time2iso;
166    $dt =~ s/[^\d]//g;
167
168    my $filename = sprintf(
169        'bookmarks-%s-%s.csv',
170        join('_', @{ $self->tags }),
171        $dt,
172    );
173
174    return [200, ['Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => sprintf('attachement; filename="%s"', $filename)], [$text]];
175}
176
177sub as_html {
178    my $self = shift;
179
180    require Template;
181    my $template = Template->new;
182
183    my @all_tags = $self->bookmarks->get_tags({ selected => @{ $self->tags }[0] });
184    my @cotags = $self->bookmarks->get_cotags({ search => $self->search });
185
186    $template->process(
187        'list.tt',
188        {
189            base_url     => $self->bookmarks->base_uri,
190            title        => $self->title,
191            query        => $self->query,
192            selected_tag => @{ $self->tags }[0],
193            search_tags  => $self->tags,
194            links        => [ $self->_get_list_links('text/html', { q => $self->query, tag => $self->tags }) ],
195            all_tags     => \@all_tags,
196            cotags       => \@cotags,
197            resources    => $self->results,
198        },
199        \my $output,
200    );
201    return [200, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]];
202}
203
204sub as_atom {
205    my $self = shift;
206
207    require XML::Atom;
208    $XML::Atom::DefaultVersion = "1.0";
209
210    require XML::Atom::Feed;
211    require XML::Atom::Entry;
212    require XML::Atom::Link;
213    require XML::Atom::Category;
214
215    my $feed = XML::Atom::Feed->new;
216    $feed->title($self->title);
217
218    for my $link ($self->_get_list_links('application/atom+xml', { q => $self->query, tag => $self->tags })) {
219        my $atom_link = XML::Atom::Link->new;
220        $atom_link->type($link->{type});
221        $atom_link->rel($link->{rel});
222        $atom_link->href($link->{href}->canonical);
223        $feed->add_link($atom_link);
224    }
225
226    for my $bookmark (@{ $self->results }) {
227        my $entry = XML::Atom::Entry->new;
228        $entry->id($bookmark->bookmark_uri->canonical);
229        $entry->title($bookmark->title);
230       
231        my $link = XML::Atom::Link->new;
232        $link->href($bookmark->uri);
233        $entry->add_link($link);
234       
235        $entry->summary('Tags: ' . join(', ', @{ $bookmark->tags }));
236
237        my $cdatetime = time2isoz $bookmark->ctime;
238        my $mdatetime = time2isoz $bookmark->mtime;
239        # make the timestamp W3C-correct
240        s/ /T/ foreach ($cdatetime, $mdatetime);
241        $entry->published($cdatetime);
242        $entry->updated($mdatetime);
243       
244        for my $tag (@{ $bookmark->tags }) {
245            my $category = XML::Atom::Category->new;
246            $category->term($tag);
247            $entry->add_category($category);
248        }
249
250        $feed->add_entry($entry);
251    }
252
253    return [200, ['Content-Type' => 'application/atom+xml; charset=UTF-8'], [$feed->as_xml]];
254}
255
2561;
Note: See TracBrowser for help on using the repository browser.