File Coverage

lib/Kwiki/Archive/SVK.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Kwiki::Archive::SVK;
2 1     1   23269 use Kwiki::Archive -Base;
  0            
  0            
3             our $VERSION = '0.12';
4              
5             use strict;
6             use warnings;
7             use SVK;
8             use SVK::XD;
9             use SVK::Util qw( traverse_history );
10             use SVN::Repos;
11             use File::Glob;
12             use Time::Local;
13              
14             sub generate {
15             super;
16              
17             my $rcs_dump = $self->export_rcs;
18             my $path = $self->plugin_directory;
19              
20             if (-d $path and File::Glob::bsd_glob("$path/*")) {
21             rename($path => $path.'.rcs-old')
22             or die "Cannot rename '".$self->plugin_directory."': $!";
23             }
24             else {
25             unlink $path;
26             }
27              
28             SVN::Repos::create(
29             $self->plugin_directory, undef, undef, undef, {
30             ($SVN::Core::VERSION =~ /^1\.0/) ? (
31             'bdb-txn-nosync' => '1',
32             'bdb-log-autoremove' => '1',
33             ) : (
34             'fs-type' => 'fsfs',
35             )
36             }
37             );
38              
39             $self->import_rcs($rcs_dump) if $rcs_dump;
40             }
41              
42             sub import_rcs {
43             my $rcs_dump = shift;
44             my $page = $self->hub->pages->page_class->new;
45             my $meta = $self->hub->pages->meta_class->new;
46              
47             foreach my $id (sort keys %$rcs_dump) {
48             local $SIG{__WARN__} = sub { 1 };
49             print STDERR "Storing $id";
50             my $history = $rcs_dump->{$id};
51             $page->id($id);
52             $meta->id($id);
53             foreach my $info (reverse @$history) {
54             print STDERR ".";
55             $page->content(delete $info->{content});
56             $meta->from_hash($info);
57             $page->metadata($meta);
58             $page->store;
59             }
60             print STDERR "\n";
61             }
62             }
63              
64             sub export_rcs {
65             my @files = File::Glob::bsd_glob(
66             io->catfile($self->plugin_directory, '*,v')->absolute
67             ) or return;
68              
69             require Kwiki::Archive::Rcs;
70             my $rcs = Kwiki::Archive::Rcs->new;
71             my $page = $self->hub->pages->page_class->new;
72              
73             return {
74             map {
75             print STDERR "Loading $_...\n";
76             $page->id($_);
77             my $history = $rcs->history($page);
78             $_->{content} = $rcs->fetch($page, delete $_->{revision_id})
79             foreach @$history;
80             ($page->id => $history);
81             } map {
82             m{([^\\/]+),v$} ? $1 : ()
83             } @files
84             }
85             }
86              
87             sub empty {
88             not io->catfile($self->plugin_directory, 'format')->exists;
89             }
90              
91             sub attachments_upload {
92             my ($attachments, $page_id, $file, $message) = @_;
93              
94             my $co_file = io->catfile(
95             $attachments->plugin_directory, $page_id, $file
96             )->absolute;
97              
98             $self->svk(
99             $attachments,
100             mkdir => [ -m => "", "//attachments/$page_id" ],
101             add => [ $co_file ],
102             commit => [ -m => "$message", $co_file ]
103             );
104             }
105              
106             sub attachments_list {
107             my ($attachments, $page_id) = @_;
108              
109             my $out = $self->svk(
110             $attachments,
111             list => [ "//attachments/$page_id" ],
112             );
113              
114             $self->svk(
115             $attachments,
116             map (
117             (revert => [
118             io->catfile(
119             $attachments->plugin_directory,
120             $page_id,
121             $_,
122             )->absolute
123             ]),
124             split(/\n/, $out)
125             ),
126             );
127             }
128              
129             sub attachments_delete {
130             my ($attachments, $page_id, $file, $message) = @_;
131             my $co_file = io->catfile(
132             $attachments->plugin_directory, $page_id, $file
133             )->absolute;
134              
135             $self->svk(
136             $attachments,
137             delete => [ $co_file ],
138             commit => [ -m => "$message", $co_file ],
139             );
140             }
141              
142             sub page_content {
143             my $page = shift;
144             my $co_file = $page->io->absolute;
145              
146             my ($atime, $mtime) = ($co_file->stat)[8, 9];
147             $self->svk( $page, up => [ $co_file ] );
148             # XXX - need better conflict resolution
149             # $self->svk( $page, revert => [ $co_file ] );
150             utime($atime, $mtime, $co_file)
151             if $mtime and $atime;
152             }
153              
154             sub page_metadata {
155             my $page = shift;
156             return;
157              
158             my $metadata = $page->{metadata};
159              
160             $metadata->from_hash($self->fetch_metadata($page));
161             $metadata->store;
162             }
163              
164             sub commit {
165             my ($page, $message) = @_;
166             my $co_file = $page->io->absolute;
167             my $props = $self->page_properties($page);
168             local $ENV{USER} = $props->{edit_by};# || $self->user_name;
169             $message = '' if not defined $message;
170              
171             # XXX - what about $props->{edit_time}?
172              
173             $self->svk(
174             $page,
175             add => [ $co_file ],
176             commit => [ -m => "$message", $co_file ],
177             );
178             }
179              
180             sub revision_numbers {
181             my $page = shift;
182             my $limit = shift;
183              
184             my $handle = $self->svk_handle($page);
185             my $fs = ($handle->{xd}->find_repos('//', 1))[2]->fs;
186             my $path = "/pages/".$page->id;
187             my @rv;
188              
189             traverse_history (
190             root => $fs->revision_root ($fs->youngest_rev),
191             path => $path,
192             cross => 0,
193             callback => sub {
194             my ($path, $rev) = @_;
195             push @rv, $rev;
196             1;
197             }
198             );
199              
200             return \@rv;
201             }
202              
203             sub fetch_metadata {
204             my ($page, $rev) = @_;
205             my $co_file = $page->io->absolute;
206              
207             $self->svk(
208             $page,
209             log => [ ($rev ? ( -r => $rev ) : ( -l => 1 )), $co_file ]
210             ) =~ /r(\d+): +(.*) \| (.+)\n\n([\d\D]+)\n/ or return {};
211              
212             return {
213             revision_id => $1,
214             edit_by => $2,
215             message => $4,
216             $self->timestamp_props($3),
217             };
218             }
219              
220             sub timestamp_props {
221             my $time = shift;
222              
223             $time =~ /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)/ or return;
224             my $gmtime = timegm($6, $5, $4, $3, $2-1, $1);
225              
226             return (
227             edit_time => scalar gmtime($gmtime),
228             edit_unixtime => $gmtime,
229             );
230             }
231              
232             sub history {
233             my $page = shift;
234              
235             return [
236             map $self->fetch_metadata($page, $_),
237             @{$self->revision_numbers($page)}
238             ];
239             }
240              
241             sub fetch {
242             my ($page, $revision_id) = @_;
243              
244             return $self->svk(
245             $page,
246             cat => [ -r => $revision_id, $page->io->absolute ],
247             );
248             }
249              
250             sub svk {
251             my $obj = shift;
252              
253             local @ENV{qw(SVKMERGE SVKDIFF LC_CTYPE LC_ALL LANG LC_MESSAGES)};
254             local *SVK::I18N::loc = *SVK::I18N::_default_gettext;
255              
256             my $svk = $self->svk_handle($obj);
257              
258             while (my $cmd = shift) {
259             my $args = shift;
260             $svk->$cmd(map "$_", @$args);
261             }
262              
263             return unless defined wantarray;
264             return $self->utf8_decode(${$svk->{output}});
265             }
266              
267             sub svk_handle {
268             my $obj = shift;
269             return $obj->{svk_handle} if $obj->{svk_handle};
270              
271             my $co_obj = Data::Hierarchy->new;
272             my $co_path = $self->plugin_directory;
273              
274             my $xd = SVK::XD->new(
275             depotmap => { '' => $co_path },
276             checkout => $co_obj,
277             svkpath => $co_path,
278             );
279              
280             my $repos = ($xd->find_repos('//', 1))[2];
281             my $svk = SVK->new(xd => $xd, output => \(my $output));
282              
283             my $subdir = $obj->class_id;
284             $subdir =~ s/s?$/s/; # pluralize the directory name
285              
286             my $method = {
287             pages => 'database_directory',
288             }->{$subdir} || 'plugin_directory';
289              
290             # mkdir $subdir if not exists -- refactor back to SVK!
291             my $fs = ($svk->{xd}->find_repos('//', 1))[2]->fs;
292             my $root = $fs->revision_root($fs->youngest_rev);
293             if ($root->check_path("/$subdir") == $SVN::Node::none) {
294             $svk->mkdir( -m => '', "//$subdir");
295             }
296              
297             $co_obj->store(
298             io($obj->$method)->absolute->pathname,
299             { depotpath => "//$subdir", revision => $repos->fs->youngest_rev },
300             );
301              
302             $obj->{svk_handle} = $svk;
303             return $svk;
304             }
305              
306             sub show_revisions {
307             my $page = $self->pages->current;
308             my $count = 0;
309              
310             my $handle = $self->svk_handle($page);
311             my $fs = ($handle->{xd}->find_repos('//', 1))[2]->fs;
312             my $path = "/pages/".$page->id;
313              
314             traverse_history (
315             root => $fs->revision_root ($fs->youngest_rev),
316             path => $path,
317             cross => 0,
318             callback => sub { $count++; 1 }
319             );
320              
321             $count-- if $count > 0;
322             return $count;
323             }
324              
325             __DATA__