Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision dc6d8231

Von Sven Schöling vor mehr als 10 Jahren hinzugefügt

  • ID dc6d82312f264df3d2bf482836ff4fd2bfe3a462
  • Vorgänger 49f5b7f7
  • Nachfolger b36f6606

Webdav: Framework um Dokumente im Webdav zu behandeln

Soll auf lange Sicht die Funktionen in Common ablösen.

Unterschiede anzeigen:

SL/Webdav.pm
package SL::Webdav;
use strict;
use parent qw(Rose::Object);
use Encode qw(decode);
use File::Spec;
use SL::Common;
use SL::Webdav::File;
use SL::Webdav::Object;
use SL::Webdav::VersionScheme::Serial;
use SL::Webdav::VersionScheme::Timestamp;
use Rose::Object::MakeMethods::Generic (
scalar => [ qw(type number) ],
'scalar --get_set_init' => [ qw(version_scheme) ],
);
my %type_to_path = (
sales_quotation => 'angebote',
sales_order => 'bestellungen',
request_quotation => 'anfragen',
purchase_order => 'lieferantenbestellungen',
sales_delivery_order => 'verkaufslieferscheine',
purchase_delivery_order => 'einkaufslieferscheine',
credit_note => 'gutschriften',
invoice => 'rechnungen',
purchase_invoice => 'einkaufsrechnungen',
part => 'waren',
service => 'dienstleistungen',
assembly => 'erzeugnisse',
);
sub get_all_files {
my ($self) = @_;
my @objects = $self->get_all_objects;
my %files_by_name;
for my $obj (@objects) {
my $filename = join '.', grep $_, $obj->basename, $obj->extension;
my $file = $files_by_name{$filename} ||= SL::Webdav::File->new(filename => $filename, webdav => $self, loaded => 1);
$file->add_objects($obj);
}
return values %files_by_name;
}
sub get_all_objects {
my ($self) = @_;
my $path = $self->webdav_path;
my @objects;
my $base_path = $ENV{'SCRIPT_NAME'};
$base_path =~ s|[^/]+$||;
if (opendir my $dir, $path) {
foreach my $file (sort { lc $a cmp lc $b } map { decode("UTF-8", $_) } readdir $dir) {
next if (($file eq '.') || ($file eq '..'));
my $fname = $file;
$fname =~ s|.*/||;
push @objects, SL::Webdav::Object->new(filename => $fname, webdav => $self);
}
closedir $dir;
return @objects;
}
}
sub get_all_latest {
my ($self) = @_;
my @files = $self->get_all_files;
map { ($_->versions)[-1] } @files;
}
sub _sanitized_number {
my $number = $_[0]->number;
$number =~ s|[/\\]|_|g;
$number;
}
sub webdav_path {
my ($self) = @_;
die "No client set in \$::auth" unless $::auth->client;
die "Need number" unless $self->number;
my $type = $type_to_path{$self->type};
die "Unknown type" unless $type;
my $path = File::Spec->catdir("webdav", $::auth->client->{id}, $type, $self->_sanitized_number);
if (!-d $path) {
Common::mkdir_with_parents($path);
}
return $path;
}
sub init_version_scheme {
SL::Webdav::VersionScheme::Timestamp->new;
}
1;
__END__
=encoding utf-8
=head1 NAME
SL::Webdav - Webdav manipulation
=head1 SYNOPSIS
# get list of all documents for this record
use SL::Webdav;
my $webdav = SL::Webdav->new(
type => 'part',
number => $number,
);
# gives you SL::Webdav::File instances
my $webdav_files = $webdav->get_all_files;
# gives you the objects instead
my $webdav_objects = $webdav->get_all_objects;
# gives you only the latest objects
my $webdav_objects = $webdav->get_all_latest;
# physical path to this dir
my $path = $webdav->webdav_path;
=head1 DESCRIPTION
This module is a wrapper around the webdav storage mechanism with some simple
document management functionality.
This is not a replacement for real document management, mostly because the
underlying webdav storage ist not fully under our control. It's common practice
to allow people direct samba access to the webdav, so all versioning
information need to be encoded into the filename of a file, and nonsensical
filenames must not break assumptions.
This module is intended to be used if you need to scan the folder for
previously saved files and need to build a list to display for it.
If you need to manipulate the versions of a file, see L<SL::Webdav::File>
If you need to access a file directly for download or metadata, see L<SL::Webdav::Object>
=head1 FUNCTIONS
=over 4
=item C<get_all_objects>
Returns all L<SL::Webdav::Objects> found.
=item C<get_all_files>
Returns all objects sorted into L<SL::Webdav::File>s.
=item C<get_all_latest>
Returns only the latest object of each L<SL::Webdav::File> found.
=item C<webdav_path>
Returns the physical path to this webdav object.
=back
=head1 VERSIONING SCHEME
You may register a versioning scheme object to hangdle versioning. It is
expected to implement the following methods:
=over 4
=item C<separator>
Must return a string that will be used to separate basename and version part of
filenames in generating and parsing.
=item C<extract_regexp>
Must return a regexp that will match a versioning string at the end of a
filename after the extension has been stripped off. It will be surrounded by
captures.
=item C<cmp>
Must return a comparison function that will be invoked with two
L<SL::Webdav::Object> instances.
=item C<first_version>
Must return a string representing the version of the first of a series of objects.
May return undef.
=item C<next_version>
Will be called with the latest L<SL::Webdav::Object> and must return a new version string.
=item C<keep_last_version>
Will be called with the latest L<SL::Webdav::Object>. Truish return value will
cause the latest object to be overwritten instead of creating a new version.
=back
=head1 BUGS AND CAVEATS
=over 4
=item *
File operations are inconsistently L<File::Spec>ed.
=back
=head1 SEE ALSO
L<SL::Webdav::File>, L<SL::Webdav::Object>
=head1 AUTHOR
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
=cut
SL/Webdav/File.pm
package SL::Webdav::File;
use strict;
use parent qw(Rose::Object);
use File::Spec;
use Rose::Object::MakeMethods::Generic (
scalar => [ qw(webdav filename loaded) ],
array => [
qw(objects),
add_objects => { interface => 'push', hash_key => 'objects' },
],
);
sub versions {
$_[0]->load unless $_[0]->loaded;
my $cmp = $_[0]->webdav->version_scheme->cmp;
sort { $cmp->($a, $b) } $_[0]->objects;
}
sub latest_version {
($_[0]->versions)[-1]
}
sub load {
my ($self) = @_;
my @objects = $self->webdav->get_all_objects;
my $ref = SL::Webdav::Object->new(filename => $self->filename, webdav => $self->webdav);
my ($ref_basename, undef, $ref_extension) = $ref->parse_filename;
$self->objects(grep { $_->basename eq $ref_basename && $_->extension eq $ref_extension } @objects);
$self->loaded(1);
}
sub store {
my ($self, %params) = @_;
$self->load unless $self->loaded;
my $last = $self->latest_version;
my $object;
if (!$last) {
my $new_version = $self->webdav->version_scheme->first_version;
$object = SL::Webdav::Object->new(filename => $self->filename, webdav => $self->webdav);
$self->add_objects($object);
} else {
if (!$self->webdav->version_scheme->keep_last_version($last)) {
$params{new_version} = 1;
}
if ($params{new_version}) {
my $new_version = $self->webdav->version_scheme->next_version($last);
my $sep = $self->webdav->version_scheme->separator;
my $new_filename = $last->basename . $sep . $new_version . "." . $last->extension;
$object = SL::Webdav::Object->new(filename => $new_filename, webdav => $self->webdav);
$self->add_objects($object);
} else {
$object = $last;
}
}
open my $fh, '>:raw', $object->full_filedescriptor or die "could not open " . $object->filename . ": $!";
$fh->print(${ $params{data} });
close $fh;
return $object;
}
1;
__END__
=encoding utf-8
=head1 NAME
SL::Webdav::File - Webdav file manipulation
=head1 SYNOPSIS
use SL::Webdav::File;
my $webdav_file = SL::Webdav::File->new(
webdav => $webdav, # SL::Webdav instance
filename => 'technical_drawing_AB28375.pdf',
);
# get existing versioned files
my @webdav_objects = $webdav_file->versions;
# store new version
my $data = SL::Helper::CreatePDF->create_pdf(...);
my $webdav_object = $webdav_file->store(data => \$data);
# force new version
my $webdav_object = $webdav_file->store(data => \$data, new_version => 1);
=head1 DESCRIPTION
A file in this context is the collection of all versions of a single file saved
into the webdav. This module provides methods to access and manipulate these
objects.
=head1 FUNCTIONS
=over 4
=item C<versions>
Will return all L<SL::Webdav::Object>s found in this file, sorted by version
according to the version scheme used.
=item C<latest_version>
Returns only the latest version object.
=item C<load>
Loads objects from disk.
=item C<store PARAMS>
Store a new version on disk. C<data> is expected to contain a reference to the
data to be written in raw encoding.
If param C<new_version> is set, force a new version, even if the versioning
scheme would keep the old one.
=back
=head1 SEE ALSO
L<SL::Webdav>, L<SL::Webdav::Object>
=head1 BUGS
None yet :)
=head1 AUTHOR
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
=cut
SL/Webdav/Object.pm
package SL::Webdav::Object;
use strict;
use parent qw(Rose::Object);
use DateTime;
use Rose::Object::MakeMethods::Generic (
scalar => [ qw(filename webdav) ],
'scalar --get_set_init' => [ qw(version basename extension) ],
);
sub init_basename {
($_[0]->parse_filename)[0];
}
sub init_version {
($_[0]->parse_filename)[1];
}
sub init_extension {
($_[0]->parse_filename)[2];
}
sub parse_filename {
my ($self) = @_;
my $name = $self->filename;
my $version_re = $self->webdav->version_scheme->extract_regexp;
my $sep = $self->webdav->version_scheme->separator;
my $extension = $name =~ s/\.(\w+?)$// ? $1 : '';
my $version = $name =~ s/\Q$sep\E($version_re)$// ? $1 : '';
my $basename = $name;
return ($basename, $version, $extension);
}
sub full_filedescriptor {
my ($self) = @_;
File::Spec->catfile($self->webdav->webdav_path, $self->filename);
}
sub atime {
DateTime->from_epoch(epoch => ($_[0]->stat)[8]);
}
sub mtime {
DateTime->from_epoch(epoch => ($_[0]->stat)[9]);
}
sub data {
my ($self) = @_;
open my $fh, '<:raw', $self->full_filedescriptor or die "could not open " . $self->filename . ": $!";
local $/ = undef;
my $data = <$fh>;
close $fh;
return \$data;
}
sub stat {
my $file = $_[0]->full_filedescriptor;
stat($file);
}
sub href {
my ($self) = @_;
my $base_path = $ENV{'SCRIPT_NAME'};
$base_path =~ s|[^/]+$||;
my $file = $self->filename;
my $path = $self->webdav->webdav_path;
my $is_directory = -d "$path/$file";
$file = join('/', map { $::form->escape($_) } grep { $_ } split m|/+|, "$path/$file");
$file .= '/' if ($is_directory);
return "$base_path/$file";
}
1;
__END__
=encoding utf-8
=head1 NAME
SL::Webdav::Object - Webdav object wrapper
=head1 SYNOPSIS
use SL::Webdav::Object;
my $object = SL::Webdav::Object->new(filename => $filename, webdav => $webdav);
my $data_ref = $object->data;
my $mtime = $object->mtime;
my $basename = $object->basename;
my $version = $object->version;
my $extension = $object->extension;
my $link = $object->href;
=head1 DESCRIPTION
This is a wrapper around a single object in the webdav. These objects are
thought about as immutable, and all manipulation will instead happen in the
associated L<SL::Webdav::File>.
=head1 FUNCTIONS
=over 4
=item C<basename>
Returns the basename with version and extension stripped.
=item C<version>
Returns the version string.
=item C<extension>
Returns the extension.
=item C<atime>
L<DateTime> wrapped stat[8]
=item C<mtime>
L<DateTime> wrapped stat[9]
=item C<data>
Ref to the actual data in raw encoding.
=item C<href>
URL relative to the web base dir for download.
=back
=head1 SEE ALSO
L<SL::Webdav>, L<SL::Webdav::File>
=head1 BUGS
None yet :)
=head1 AUTHOR
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
=cut
SL/Webdav/VersionScheme/Serial.pm
package SL::Webdav::VersionScheme::Serial;
use strict;
use parent qw(Rose::Object);
use DateTime;
sub separator { "-" }
sub extract_regexp { qr/\d+/ }
sub cmp { sub { $_[0]->version <=> $_[1]->version } }
sub first_version { }
sub next_version { $_[1]->version + 1 }
sub keep_last_version {
my ($self, $last) = @_;
if ($::lxoffice_conf->{webdav}{new_version_after_minutes}) {
return DateTime->now <= $last->mtime + DateTime::Duration->new(minutes => $::lx_office_conf{webdav}{new_version_after_minutes});
} else {
return 0;
}
}
1;
SL/Webdav/VersionScheme/Timestamp.pm
package SL::Webdav::VersionScheme::Timestamp;
use strict;
use parent qw(Rose::Object);
use POSIX;
sub separator { "_" }
sub extract_regexp { qr/\d{8}_\d{6}/ }
sub cmp { sub { $_[0]->version cmp $_[1]->version } }
sub first_version { goto &get_current_formatted_time }
sub next_version { goto &get_current_formatted_time }
sub keep_last_version {
0;
}
sub get_current_formatted_time {
return POSIX::strftime('%Y%m%d_%H%M%S', localtime());
}
1;

Auch abrufbar als: Unified diff