Revision dc6d8231
Von Sven Schöling vor fast 10 Jahren hinzugefügt
SL/Webdav.pm | ||
---|---|---|
1 |
package SL::Webdav; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use parent qw(Rose::Object); |
|
5 |
|
|
6 |
use Encode qw(decode); |
|
7 |
use File::Spec; |
|
8 |
use SL::Common; |
|
9 |
use SL::Webdav::File; |
|
10 |
use SL::Webdav::Object; |
|
11 |
use SL::Webdav::VersionScheme::Serial; |
|
12 |
use SL::Webdav::VersionScheme::Timestamp; |
|
13 |
|
|
14 |
use Rose::Object::MakeMethods::Generic ( |
|
15 |
scalar => [ qw(type number) ], |
|
16 |
'scalar --get_set_init' => [ qw(version_scheme) ], |
|
17 |
); |
|
18 |
|
|
19 |
my %type_to_path = ( |
|
20 |
sales_quotation => 'angebote', |
|
21 |
sales_order => 'bestellungen', |
|
22 |
request_quotation => 'anfragen', |
|
23 |
purchase_order => 'lieferantenbestellungen', |
|
24 |
sales_delivery_order => 'verkaufslieferscheine', |
|
25 |
purchase_delivery_order => 'einkaufslieferscheine', |
|
26 |
credit_note => 'gutschriften', |
|
27 |
invoice => 'rechnungen', |
|
28 |
purchase_invoice => 'einkaufsrechnungen', |
|
29 |
part => 'waren', |
|
30 |
service => 'dienstleistungen', |
|
31 |
assembly => 'erzeugnisse', |
|
32 |
); |
|
33 |
|
|
34 |
sub get_all_files { |
|
35 |
my ($self) = @_; |
|
36 |
|
|
37 |
my @objects = $self->get_all_objects; |
|
38 |
my %files_by_name; |
|
39 |
|
|
40 |
for my $obj (@objects) { |
|
41 |
my $filename = join '.', grep $_, $obj->basename, $obj->extension; |
|
42 |
|
|
43 |
my $file = $files_by_name{$filename} ||= SL::Webdav::File->new(filename => $filename, webdav => $self, loaded => 1); |
|
44 |
$file->add_objects($obj); |
|
45 |
} |
|
46 |
|
|
47 |
return values %files_by_name; |
|
48 |
} |
|
49 |
|
|
50 |
sub get_all_objects { |
|
51 |
my ($self) = @_; |
|
52 |
|
|
53 |
my $path = $self->webdav_path; |
|
54 |
my @objects; |
|
55 |
|
|
56 |
my $base_path = $ENV{'SCRIPT_NAME'}; |
|
57 |
$base_path =~ s|[^/]+$||; |
|
58 |
if (opendir my $dir, $path) { |
|
59 |
foreach my $file (sort { lc $a cmp lc $b } map { decode("UTF-8", $_) } readdir $dir) { |
|
60 |
next if (($file eq '.') || ($file eq '..')); |
|
61 |
|
|
62 |
my $fname = $file; |
|
63 |
$fname =~ s|.*/||; |
|
64 |
|
|
65 |
push @objects, SL::Webdav::Object->new(filename => $fname, webdav => $self); |
|
66 |
} |
|
67 |
|
|
68 |
closedir $dir; |
|
69 |
|
|
70 |
return @objects; |
|
71 |
} |
|
72 |
} |
|
73 |
|
|
74 |
sub get_all_latest { |
|
75 |
my ($self) = @_; |
|
76 |
|
|
77 |
my @files = $self->get_all_files; |
|
78 |
map { ($_->versions)[-1] } @files; |
|
79 |
} |
|
80 |
|
|
81 |
sub _sanitized_number { |
|
82 |
my $number = $_[0]->number; |
|
83 |
$number =~ s|[/\\]|_|g; |
|
84 |
$number; |
|
85 |
} |
|
86 |
|
|
87 |
sub webdav_path { |
|
88 |
my ($self) = @_; |
|
89 |
|
|
90 |
die "No client set in \$::auth" unless $::auth->client; |
|
91 |
die "Need number" unless $self->number; |
|
92 |
|
|
93 |
my $type = $type_to_path{$self->type}; |
|
94 |
|
|
95 |
die "Unknown type" unless $type; |
|
96 |
|
|
97 |
my $path = File::Spec->catdir("webdav", $::auth->client->{id}, $type, $self->_sanitized_number); |
|
98 |
|
|
99 |
if (!-d $path) { |
|
100 |
Common::mkdir_with_parents($path); |
|
101 |
} |
|
102 |
|
|
103 |
return $path; |
|
104 |
} |
|
105 |
|
|
106 |
sub init_version_scheme { |
|
107 |
SL::Webdav::VersionScheme::Timestamp->new; |
|
108 |
} |
|
109 |
|
|
110 |
1; |
|
111 |
|
|
112 |
__END__ |
|
113 |
|
|
114 |
=encoding utf-8 |
|
115 |
|
|
116 |
=head1 NAME |
|
117 |
|
|
118 |
SL::Webdav - Webdav manipulation |
|
119 |
|
|
120 |
=head1 SYNOPSIS |
|
121 |
|
|
122 |
# get list of all documents for this record |
|
123 |
use SL::Webdav; |
|
124 |
|
|
125 |
my $webdav = SL::Webdav->new( |
|
126 |
type => 'part', |
|
127 |
number => $number, |
|
128 |
); |
|
129 |
|
|
130 |
# gives you SL::Webdav::File instances |
|
131 |
my $webdav_files = $webdav->get_all_files; |
|
132 |
|
|
133 |
# gives you the objects instead |
|
134 |
my $webdav_objects = $webdav->get_all_objects; |
|
135 |
|
|
136 |
# gives you only the latest objects |
|
137 |
my $webdav_objects = $webdav->get_all_latest; |
|
138 |
|
|
139 |
# physical path to this dir |
|
140 |
my $path = $webdav->webdav_path; |
|
141 |
|
|
142 |
=head1 DESCRIPTION |
|
143 |
|
|
144 |
This module is a wrapper around the webdav storage mechanism with some simple |
|
145 |
document management functionality. |
|
146 |
|
|
147 |
This is not a replacement for real document management, mostly because the |
|
148 |
underlying webdav storage ist not fully under our control. It's common practice |
|
149 |
to allow people direct samba access to the webdav, so all versioning |
|
150 |
information need to be encoded into the filename of a file, and nonsensical |
|
151 |
filenames must not break assumptions. |
|
152 |
|
|
153 |
This module is intended to be used if you need to scan the folder for |
|
154 |
previously saved files and need to build a list to display for it. |
|
155 |
|
|
156 |
If you need to manipulate the versions of a file, see L<SL::Webdav::File> |
|
157 |
|
|
158 |
If you need to access a file directly for download or metadata, see L<SL::Webdav::Object> |
|
159 |
|
|
160 |
=head1 FUNCTIONS |
|
161 |
|
|
162 |
=over 4 |
|
163 |
|
|
164 |
=item C<get_all_objects> |
|
165 |
|
|
166 |
Returns all L<SL::Webdav::Objects> found. |
|
167 |
|
|
168 |
=item C<get_all_files> |
|
169 |
|
|
170 |
Returns all objects sorted into L<SL::Webdav::File>s. |
|
171 |
|
|
172 |
=item C<get_all_latest> |
|
173 |
|
|
174 |
Returns only the latest object of each L<SL::Webdav::File> found. |
|
175 |
|
|
176 |
=item C<webdav_path> |
|
177 |
|
|
178 |
Returns the physical path to this webdav object. |
|
179 |
|
|
180 |
=back |
|
181 |
|
|
182 |
=head1 VERSIONING SCHEME |
|
183 |
|
|
184 |
You may register a versioning scheme object to hangdle versioning. It is |
|
185 |
expected to implement the following methods: |
|
186 |
|
|
187 |
=over 4 |
|
188 |
|
|
189 |
=item C<separator> |
|
190 |
|
|
191 |
Must return a string that will be used to separate basename and version part of |
|
192 |
filenames in generating and parsing. |
|
193 |
|
|
194 |
=item C<extract_regexp> |
|
195 |
|
|
196 |
Must return a regexp that will match a versioning string at the end of a |
|
197 |
filename after the extension has been stripped off. It will be surrounded by |
|
198 |
captures. |
|
199 |
|
|
200 |
=item C<cmp> |
|
201 |
|
|
202 |
Must return a comparison function that will be invoked with two |
|
203 |
L<SL::Webdav::Object> instances. |
|
204 |
|
|
205 |
=item C<first_version> |
|
206 |
|
|
207 |
Must return a string representing the version of the first of a series of objects. |
|
208 |
|
|
209 |
May return undef. |
|
210 |
|
|
211 |
=item C<next_version> |
|
212 |
|
|
213 |
Will be called with the latest L<SL::Webdav::Object> and must return a new version string. |
|
214 |
|
|
215 |
=item C<keep_last_version> |
|
216 |
|
|
217 |
Will be called with the latest L<SL::Webdav::Object>. Truish return value will |
|
218 |
cause the latest object to be overwritten instead of creating a new version. |
|
219 |
|
|
220 |
=back |
|
221 |
|
|
222 |
=head1 BUGS AND CAVEATS |
|
223 |
|
|
224 |
=over 4 |
|
225 |
|
|
226 |
=item * |
|
227 |
|
|
228 |
File operations are inconsistently L<File::Spec>ed. |
|
229 |
|
|
230 |
=back |
|
231 |
|
|
232 |
=head1 SEE ALSO |
|
233 |
|
|
234 |
L<SL::Webdav::File>, L<SL::Webdav::Object> |
|
235 |
|
|
236 |
=head1 AUTHOR |
|
237 |
|
|
238 |
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt> |
|
239 |
|
|
240 |
=cut |
SL/Webdav/File.pm | ||
---|---|---|
1 |
package SL::Webdav::File; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use parent qw(Rose::Object); |
|
5 |
|
|
6 |
use File::Spec; |
|
7 |
|
|
8 |
use Rose::Object::MakeMethods::Generic ( |
|
9 |
scalar => [ qw(webdav filename loaded) ], |
|
10 |
array => [ |
|
11 |
qw(objects), |
|
12 |
add_objects => { interface => 'push', hash_key => 'objects' }, |
|
13 |
], |
|
14 |
); |
|
15 |
|
|
16 |
sub versions { |
|
17 |
$_[0]->load unless $_[0]->loaded; |
|
18 |
my $cmp = $_[0]->webdav->version_scheme->cmp; |
|
19 |
sort { $cmp->($a, $b) } $_[0]->objects; |
|
20 |
} |
|
21 |
|
|
22 |
sub latest_version { |
|
23 |
($_[0]->versions)[-1] |
|
24 |
} |
|
25 |
|
|
26 |
sub load { |
|
27 |
my ($self) = @_; |
|
28 |
my @objects = $self->webdav->get_all_objects; |
|
29 |
my $ref = SL::Webdav::Object->new(filename => $self->filename, webdav => $self->webdav); |
|
30 |
my ($ref_basename, undef, $ref_extension) = $ref->parse_filename; |
|
31 |
|
|
32 |
$self->objects(grep { $_->basename eq $ref_basename && $_->extension eq $ref_extension } @objects); |
|
33 |
$self->loaded(1); |
|
34 |
} |
|
35 |
|
|
36 |
sub store { |
|
37 |
my ($self, %params) = @_; |
|
38 |
|
|
39 |
$self->load unless $self->loaded; |
|
40 |
|
|
41 |
my $last = $self->latest_version; |
|
42 |
my $object; |
|
43 |
|
|
44 |
if (!$last) { |
|
45 |
my $new_version = $self->webdav->version_scheme->first_version; |
|
46 |
$object = SL::Webdav::Object->new(filename => $self->filename, webdav => $self->webdav); |
|
47 |
|
|
48 |
$self->add_objects($object); |
|
49 |
} else { |
|
50 |
if (!$self->webdav->version_scheme->keep_last_version($last)) { |
|
51 |
$params{new_version} = 1; |
|
52 |
} |
|
53 |
|
|
54 |
if ($params{new_version}) { |
|
55 |
my $new_version = $self->webdav->version_scheme->next_version($last); |
|
56 |
my $sep = $self->webdav->version_scheme->separator; |
|
57 |
my $new_filename = $last->basename . $sep . $new_version . "." . $last->extension; |
|
58 |
$object = SL::Webdav::Object->new(filename => $new_filename, webdav => $self->webdav); |
|
59 |
|
|
60 |
$self->add_objects($object); |
|
61 |
} else { |
|
62 |
$object = $last; |
|
63 |
} |
|
64 |
} |
|
65 |
|
|
66 |
open my $fh, '>:raw', $object->full_filedescriptor or die "could not open " . $object->filename . ": $!"; |
|
67 |
|
|
68 |
$fh->print(${ $params{data} }); |
|
69 |
|
|
70 |
close $fh; |
|
71 |
|
|
72 |
return $object; |
|
73 |
} |
|
74 |
|
|
75 |
1; |
|
76 |
|
|
77 |
__END__ |
|
78 |
|
|
79 |
=encoding utf-8 |
|
80 |
|
|
81 |
=head1 NAME |
|
82 |
|
|
83 |
SL::Webdav::File - Webdav file manipulation |
|
84 |
|
|
85 |
=head1 SYNOPSIS |
|
86 |
|
|
87 |
use SL::Webdav::File; |
|
88 |
|
|
89 |
my $webdav_file = SL::Webdav::File->new( |
|
90 |
webdav => $webdav, # SL::Webdav instance |
|
91 |
filename => 'technical_drawing_AB28375.pdf', |
|
92 |
); |
|
93 |
|
|
94 |
# get existing versioned files |
|
95 |
my @webdav_objects = $webdav_file->versions; |
|
96 |
|
|
97 |
# store new version |
|
98 |
my $data = SL::Helper::CreatePDF->create_pdf(...); |
|
99 |
my $webdav_object = $webdav_file->store(data => \$data); |
|
100 |
|
|
101 |
# force new version |
|
102 |
my $webdav_object = $webdav_file->store(data => \$data, new_version => 1); |
|
103 |
|
|
104 |
=head1 DESCRIPTION |
|
105 |
|
|
106 |
A file in this context is the collection of all versions of a single file saved |
|
107 |
into the webdav. This module provides methods to access and manipulate these |
|
108 |
objects. |
|
109 |
|
|
110 |
=head1 FUNCTIONS |
|
111 |
|
|
112 |
=over 4 |
|
113 |
|
|
114 |
=item C<versions> |
|
115 |
|
|
116 |
Will return all L<SL::Webdav::Object>s found in this file, sorted by version |
|
117 |
according to the version scheme used. |
|
118 |
|
|
119 |
=item C<latest_version> |
|
120 |
|
|
121 |
Returns only the latest version object. |
|
122 |
|
|
123 |
=item C<load> |
|
124 |
|
|
125 |
Loads objects from disk. |
|
126 |
|
|
127 |
=item C<store PARAMS> |
|
128 |
|
|
129 |
Store a new version on disk. C<data> is expected to contain a reference to the |
|
130 |
data to be written in raw encoding. |
|
131 |
|
|
132 |
If param C<new_version> is set, force a new version, even if the versioning |
|
133 |
scheme would keep the old one. |
|
134 |
|
|
135 |
=back |
|
136 |
|
|
137 |
=head1 SEE ALSO |
|
138 |
|
|
139 |
L<SL::Webdav>, L<SL::Webdav::Object> |
|
140 |
|
|
141 |
=head1 BUGS |
|
142 |
|
|
143 |
None yet :) |
|
144 |
|
|
145 |
=head1 AUTHOR |
|
146 |
|
|
147 |
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt> |
|
148 |
|
|
149 |
=cut |
SL/Webdav/Object.pm | ||
---|---|---|
1 |
package SL::Webdav::Object; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use parent qw(Rose::Object); |
|
5 |
|
|
6 |
use DateTime; |
|
7 |
|
|
8 |
use Rose::Object::MakeMethods::Generic ( |
|
9 |
scalar => [ qw(filename webdav) ], |
|
10 |
'scalar --get_set_init' => [ qw(version basename extension) ], |
|
11 |
); |
|
12 |
|
|
13 |
sub init_basename { |
|
14 |
($_[0]->parse_filename)[0]; |
|
15 |
} |
|
16 |
|
|
17 |
sub init_version { |
|
18 |
($_[0]->parse_filename)[1]; |
|
19 |
} |
|
20 |
|
|
21 |
sub init_extension { |
|
22 |
($_[0]->parse_filename)[2]; |
|
23 |
} |
|
24 |
|
|
25 |
sub parse_filename { |
|
26 |
my ($self) = @_; |
|
27 |
|
|
28 |
my $name = $self->filename; |
|
29 |
my $version_re = $self->webdav->version_scheme->extract_regexp; |
|
30 |
my $sep = $self->webdav->version_scheme->separator; |
|
31 |
|
|
32 |
my $extension = $name =~ s/\.(\w+?)$// ? $1 : ''; |
|
33 |
my $version = $name =~ s/\Q$sep\E($version_re)$// ? $1 : ''; |
|
34 |
my $basename = $name; |
|
35 |
|
|
36 |
return ($basename, $version, $extension); |
|
37 |
} |
|
38 |
|
|
39 |
sub full_filedescriptor { |
|
40 |
my ($self) = @_; |
|
41 |
|
|
42 |
File::Spec->catfile($self->webdav->webdav_path, $self->filename); |
|
43 |
} |
|
44 |
|
|
45 |
sub atime { |
|
46 |
DateTime->from_epoch(epoch => ($_[0]->stat)[8]); |
|
47 |
} |
|
48 |
|
|
49 |
sub mtime { |
|
50 |
DateTime->from_epoch(epoch => ($_[0]->stat)[9]); |
|
51 |
} |
|
52 |
|
|
53 |
sub data { |
|
54 |
my ($self) = @_; |
|
55 |
|
|
56 |
open my $fh, '<:raw', $self->full_filedescriptor or die "could not open " . $self->filename . ": $!"; |
|
57 |
|
|
58 |
local $/ = undef; |
|
59 |
|
|
60 |
my $data = <$fh>; |
|
61 |
|
|
62 |
close $fh; |
|
63 |
|
|
64 |
return \$data; |
|
65 |
} |
|
66 |
|
|
67 |
sub stat { |
|
68 |
my $file = $_[0]->full_filedescriptor; |
|
69 |
stat($file); |
|
70 |
} |
|
71 |
|
|
72 |
sub href { |
|
73 |
my ($self) = @_; |
|
74 |
|
|
75 |
my $base_path = $ENV{'SCRIPT_NAME'}; |
|
76 |
$base_path =~ s|[^/]+$||; |
|
77 |
|
|
78 |
my $file = $self->filename; |
|
79 |
my $path = $self->webdav->webdav_path; |
|
80 |
my $is_directory = -d "$path/$file"; |
|
81 |
|
|
82 |
$file = join('/', map { $::form->escape($_) } grep { $_ } split m|/+|, "$path/$file"); |
|
83 |
$file .= '/' if ($is_directory); |
|
84 |
|
|
85 |
return "$base_path/$file"; |
|
86 |
} |
|
87 |
|
|
88 |
1; |
|
89 |
|
|
90 |
__END__ |
|
91 |
|
|
92 |
=encoding utf-8 |
|
93 |
|
|
94 |
=head1 NAME |
|
95 |
|
|
96 |
SL::Webdav::Object - Webdav object wrapper |
|
97 |
|
|
98 |
=head1 SYNOPSIS |
|
99 |
|
|
100 |
use SL::Webdav::Object; |
|
101 |
|
|
102 |
my $object = SL::Webdav::Object->new(filename => $filename, webdav => $webdav); |
|
103 |
|
|
104 |
my $data_ref = $object->data; |
|
105 |
my $mtime = $object->mtime; |
|
106 |
|
|
107 |
my $basename = $object->basename; |
|
108 |
my $version = $object->version; |
|
109 |
my $extension = $object->extension; |
|
110 |
|
|
111 |
my $link = $object->href; |
|
112 |
|
|
113 |
=head1 DESCRIPTION |
|
114 |
|
|
115 |
This is a wrapper around a single object in the webdav. These objects are |
|
116 |
thought about as immutable, and all manipulation will instead happen in the |
|
117 |
associated L<SL::Webdav::File>. |
|
118 |
|
|
119 |
=head1 FUNCTIONS |
|
120 |
|
|
121 |
=over 4 |
|
122 |
|
|
123 |
=item C<basename> |
|
124 |
|
|
125 |
Returns the basename with version and extension stripped. |
|
126 |
|
|
127 |
=item C<version> |
|
128 |
|
|
129 |
Returns the version string. |
|
130 |
|
|
131 |
=item C<extension> |
|
132 |
|
|
133 |
Returns the extension. |
|
134 |
|
|
135 |
=item C<atime> |
|
136 |
|
|
137 |
L<DateTime> wrapped stat[8] |
|
138 |
|
|
139 |
=item C<mtime> |
|
140 |
|
|
141 |
L<DateTime> wrapped stat[9] |
|
142 |
|
|
143 |
=item C<data> |
|
144 |
|
|
145 |
Ref to the actual data in raw encoding. |
|
146 |
|
|
147 |
=item C<href> |
|
148 |
|
|
149 |
URL relative to the web base dir for download. |
|
150 |
|
|
151 |
=back |
|
152 |
|
|
153 |
=head1 SEE ALSO |
|
154 |
|
|
155 |
L<SL::Webdav>, L<SL::Webdav::File> |
|
156 |
|
|
157 |
=head1 BUGS |
|
158 |
|
|
159 |
None yet :) |
|
160 |
|
|
161 |
=head1 AUTHOR |
|
162 |
|
|
163 |
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt> |
|
164 |
|
|
165 |
=cut |
SL/Webdav/VersionScheme/Serial.pm | ||
---|---|---|
1 |
package SL::Webdav::VersionScheme::Serial; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use parent qw(Rose::Object); |
|
5 |
|
|
6 |
use DateTime; |
|
7 |
|
|
8 |
sub separator { "-" } |
|
9 |
|
|
10 |
sub extract_regexp { qr/\d+/ } |
|
11 |
|
|
12 |
sub cmp { sub { $_[0]->version <=> $_[1]->version } } |
|
13 |
|
|
14 |
sub first_version { } |
|
15 |
|
|
16 |
sub next_version { $_[1]->version + 1 } |
|
17 |
|
|
18 |
sub keep_last_version { |
|
19 |
my ($self, $last) = @_; |
|
20 |
|
|
21 |
if ($::lxoffice_conf->{webdav}{new_version_after_minutes}) { |
|
22 |
return DateTime->now <= $last->mtime + DateTime::Duration->new(minutes => $::lx_office_conf{webdav}{new_version_after_minutes}); |
|
23 |
} else { |
|
24 |
return 0; |
|
25 |
} |
|
26 |
} |
|
27 |
|
|
28 |
1; |
SL/Webdav/VersionScheme/Timestamp.pm | ||
---|---|---|
1 |
package SL::Webdav::VersionScheme::Timestamp; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use parent qw(Rose::Object); |
|
5 |
|
|
6 |
use POSIX; |
|
7 |
|
|
8 |
sub separator { "_" } |
|
9 |
|
|
10 |
sub extract_regexp { qr/\d{8}_\d{6}/ } |
|
11 |
|
|
12 |
sub cmp { sub { $_[0]->version cmp $_[1]->version } } |
|
13 |
|
|
14 |
sub first_version { goto &get_current_formatted_time } |
|
15 |
|
|
16 |
sub next_version { goto &get_current_formatted_time } |
|
17 |
|
|
18 |
sub keep_last_version { |
|
19 |
0; |
|
20 |
} |
|
21 |
|
|
22 |
sub get_current_formatted_time { |
|
23 |
return POSIX::strftime('%Y%m%d_%H%M%S', localtime()); |
|
24 |
} |
|
25 |
|
|
26 |
1; |
Auch abrufbar als: Unified diff
Webdav: Framework um Dokumente im Webdav zu behandeln
Soll auf lange Sicht die Funktionen in Common ablösen.