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

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