1package SVN::Web::Browse;
2
3use strict;
4use warnings;
5
6use base 'SVN::Web::action';
7
8use Encode ();
9use SVN::Ra;
10use SVN::Client;
11use SVN::Web::X;
12
13our $VERSION = 0.63;
14
15=head1 NAME
16
17SVN::Web::Browse - SVN::Web action to browse a Subversion repository
18
19=head1 SYNOPSIS
20
21In F<config.yaml>
22
23  actions:
24    ...
25    browse:
26      class: SVN::Web::Browse
27      action_menu:
28        show:
29          - directory
30        link_text: (browse directory)
31    ...
32
33=head1 DESCRIPTION
34
35Returns a file/directory listing for the given repository path.
36
37=head1 OPTIONS
38
39=over 4
40
41=item rev
42
43The repository revision to show.  Defaults to the repository's youngest
44revision.
45
46=back
47
48=head1 TEMPLATE VARIABLES
49
50=over 4
51
52=item at_head
53
54A boolean value, indicating whether or not the user is currently
55browsing the HEAD of the repository.
56
57=item context
58
59Always C<directory>.
60
61=item entries
62
63A list of hash refs, one for each file and directory entry in the browsed
64path.  The list is ordered with directories first, then files, sorted
65alphabetically.
66
67Each hash ref has the following keys.
68
69=over 8
70
71=item name
72
73The entry's name.
74
75=item path
76
77The entry's full path.
78
79=item rev
80
81The entry's most recent interesting revision.
82
83=item size
84
85The entry's size, in bytes.  The empty string C<''> for directories.
86
87=item type
88
89The entry's C<svn:mime-type> property.  Not set for directories.
90
91=item author
92
93The userid that committed the most recent interesting revision for this
94entry.
95
96=item date
97
98The date of the entry's most recent interesting revision, formatted
99according to L<SVN::Web/"Time and date formatting">.
100
101=item msg
102
103The log message for the entry's most recent interesting revision.
104
105=back
106
107=item rev
108
109The repository revision that is being browsed.  Will be the same as the
110C<rev> parameter given to the action, unless that parameter was not set,
111in which case it will be the repository's youngest revision.
112
113=item youngest_rev
114
115The repository's youngest revision.
116
117=back
118
119=head1 EXCEPTIONS
120
121=over 4
122
123=item (path %1 does not exist in revision %2)
124
125The given path is not present in the repository at the given revision.
126
127=item (path %1 is not a directory in revision %2)
128
129The given path exists in the repository at the given revision, but is
130not a directory.  This action is only used to browse directories.
131
132=back
133
134=cut
135
136sub cache_key {
137    my $self = shift;
138    my $path = $self->{path};
139
140    my ( undef, undef, $act_rev, $at_head ) = $self->get_revs();
141
142    return "$act_rev:$at_head:$path";
143}
144
145sub run {
146    my $self = shift;
147    my $uri = $self->{repos}{uri};
148    $uri .= '/'.$self->rpath if $self->rpath;
149
150    my ( $exp_rev, $yng_rev, $act_rev, $at_head ) = $self->get_revs();
151
152    my $rev = $act_rev;
153
154    my $node_kind = $self->svn_get_node_kind($uri, $rev, $rev);
155
156    if ( $node_kind == $SVN::Node::none ) {
157        SVN::Web::X->throw(
158            error => '(path %1 does not exist in revision %2)',
159            vars  => [ $self->rpath, $rev ]
160        );
161    }
162
163    if ( $node_kind != $SVN::Node::dir ) {
164        SVN::Web::X->throw(
165            error => '(path %1 is not a directory in revision %2)',
166            vars  => [ $self->rpath, $rev ],
167        );
168    }
169
170    my $dirents = $self->ctx_ls( $uri, $rev, 0 );
171
172    my $entries = [];
173    my $current_time = time();
174
175    my $base_path = $self->rpath;
176    while ( my ( $svn_name, $dirent ) = each %{$dirents} ) {
177        my $name = Encode::decode('utf8',$svn_name);
178        my $node_kind = $dirent->kind();
179
180        my @log_result = $self->recent_interesting_rev( "$base_path/$name", $rev );
181
182        push @{$entries},
183          {
184            name      => $name,
185            rev       => $log_result[1],
186            kind      => $node_kind,
187            isdir     => ( $node_kind == $SVN::Node::dir ),
188            size      => ( $node_kind == $SVN::Node::dir ? '' : $dirent->size() ),
189            author    => $dirent->last_author(),
190            has_props => $dirent->has_props(),
191            time      => $dirent->time() / 1_000_000,
192            age       => $current_time - ( $dirent->time() / 1_000_000 ),
193            msg       => Encode::decode('utf8',$log_result[4]),
194          };
195    }
196
197    # TODO: custom sorting
198    @$entries =
199      sort { ( $b->{isdir} <=> $a->{isdir} ) || ( $a->{name} cmp $b->{name} ) }
200      @$entries;
201
202    my @props = ();
203    foreach my $prop_name (qw(svn:externals)) {
204        my $prop_value = ( $self->ctx_revprop_get( $prop_name, $uri, $rev ) )[0];
205        if ( defined $prop_value ) {
206            $prop_value =~ s/\s*\n$//ms;
207            push @props, { name => $prop_name, value => $prop_value };
208        }
209    }
210
211    return {
212        template => 'browse',
213        data     => {
214            context      => 'directory',
215            entries      => $entries,
216            rev          => $act_rev,
217            youngest_rev => $yng_rev,
218            at_head      => $at_head,
219            props        => \@props,
220        }
221    };
222}
223
2241;
225
226=head1 COPYRIGHT
227
228Copyright 2003-2004 by Chia-liang Kao C<< <clkao@clkao.org> >>.
229
230Copyright 2005-2007 by Nik Clayton C<< <nik@FreeBSD.org> >>.
231
232This program is free software; you can redistribute it and/or modify it
233under the same terms as Perl itself.
234
235See L<http://www.perl.com/perl/misc/Artistic.html>
236
237=cut
238