|
package SL::File::Backend::Webdav;
|
|
|
|
use strict;
|
|
|
|
use parent qw(SL::File::Backend);
|
|
use SL::DB::File;
|
|
|
|
use SL::System::Process;
|
|
use File::Copy;
|
|
use File::Slurp;
|
|
use File::Basename;
|
|
use File::Path qw(make_path);
|
|
use File::MimeInfo::Magic;
|
|
use File::stat;
|
|
|
|
#
|
|
# public methods
|
|
#
|
|
|
|
sub delete {
|
|
my ($self, %params) = @_;
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "del in backend " . $self . " file " . $params{dbfile});
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "file id=" . $params{dbfile}->id * 1);
|
|
return 0 unless $params{dbfile};
|
|
my ($file_path, undef, undef) = $self->webdav_path($params{dbfile});
|
|
unlink($file_path);
|
|
return 1;
|
|
}
|
|
|
|
sub rename {
|
|
my ($self, %params) = @_;
|
|
return 0 unless $params{dbfile};
|
|
my (undef, $oldwebdavname) = split(/:/, $params{dbfile}->location, 2);
|
|
my ($tofile, $basepath, $basename) = $self->webdav_path($params{dbfile});
|
|
my $fromfile = File::Spec->catfile($basepath, $oldwebdavname);
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "renamefrom=" . $fromfile . " to=" . $tofile);
|
|
move($fromfile, $tofile);
|
|
}
|
|
|
|
sub save {
|
|
my ($self, %params) = @_;
|
|
die 'dbfile not exists' unless $params{dbfile};
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "in backend " . $self . " file " . $params{dbfile});
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "file id=" . $params{dbfile}->id);
|
|
my $dbfile = $params{dbfile};
|
|
die 'no file contents' unless $params{file_path} || $params{file_contents};
|
|
|
|
if ($params{dbfile}->id * 1 == 0) {
|
|
|
|
# new element: need id for file
|
|
$params{dbfile}->save;
|
|
}
|
|
my ($tofile, undef, $basename) = $self->webdav_path($params{dbfile});
|
|
if ($params{file_path} && -f $params{file_path}) {
|
|
copy($params{file_path}, $tofile);
|
|
}
|
|
elsif ($params{file_contents}) {
|
|
open(OUT, "> " . $tofile);
|
|
print OUT $params{file_contents};
|
|
close(OUT);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub get_version_count {
|
|
my ($self, %params) = @_;
|
|
die "no dbfile" unless $params{dbfile};
|
|
## TODO
|
|
# Webdav doesn't have versions by now. And delete checks for version, so return 0 for now
|
|
return 0;
|
|
}
|
|
|
|
sub get_mtime {
|
|
my ($self, %params) = @_;
|
|
die "no dbfile" unless $params{dbfile};
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "version=" .$params{version});
|
|
my ($path, undef, undef) = $self->webdav_path($params{dbfile});
|
|
die "No file found in Backend: " . $path unless -f $path;
|
|
my $dt = DateTime->from_epoch(epoch => stat($path)->mtime, time_zone => $::locale->get_local_time_zone()->name)->clone();
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "dt=" .$dt);
|
|
return $dt;
|
|
}
|
|
|
|
sub get_filepath {
|
|
my ($self, %params) = @_;
|
|
die "no dbfile" unless $params{dbfile};
|
|
my ($path, undef, undef) = $self->webdav_path($params{dbfile});
|
|
die "No file found in Backend: " . $path unless -f $path;
|
|
return $path;
|
|
}
|
|
|
|
sub get_content {
|
|
my ($self, %params) = @_;
|
|
my $path = $self->get_filepath(%params);
|
|
return "" unless $path;
|
|
my $contents = File::Slurp::read_file($path);
|
|
return \$contents;
|
|
}
|
|
|
|
sub sync_from_backend {
|
|
my ($self, %params) = @_;
|
|
return unless $params{file_type};
|
|
|
|
$self->sync_all_locations(%params);
|
|
|
|
}
|
|
|
|
sub enabled {
|
|
return $::instance_conf->get_doc_webdav;
|
|
}
|
|
|
|
#
|
|
# internals
|
|
#
|
|
|
|
my %type_to_path = (
|
|
sales_quotation => 'angebote',
|
|
sales_order_intake => 'auftragseingaenge',
|
|
sales_order => 'bestellungen',
|
|
request_quotation => 'anfragen',
|
|
purchase_quotation_intake => 'angebotseingaenge',
|
|
purchase_order => 'lieferantenbestellungen',
|
|
sales_delivery_order => 'verkaufslieferscheine',
|
|
purchase_delivery_order => 'einkaufslieferscheine',
|
|
purchase_reclamation => 'einkaufsreklamation',
|
|
sales_reclamation => 'verkaufsreklamation',
|
|
supplier_delivery_order => 'beistelllieferscheine',
|
|
rma_delivery_order => 'retourenlieferscheine',
|
|
credit_note => 'gutschriften',
|
|
invoice => 'rechnungen',
|
|
invoice_for_advance_payment => 'rechnungen',
|
|
final_invoice => 'rechnungen',
|
|
purchase_invoice => 'einkaufsrechnungen',
|
|
part => 'waren',
|
|
service => 'dienstleistungen',
|
|
assembly => 'erzeugnisse',
|
|
letter => 'briefe',
|
|
general_ledger => 'dialogbuchungen',
|
|
gl_transaction => 'dialogbuchungen',
|
|
accounts_payable => 'kreditorenbuchungen',
|
|
shop_image => 'shopbilder',
|
|
customer => 'kunden',
|
|
vendor => 'lieferanten',
|
|
);
|
|
|
|
my %type_to_model = (
|
|
sales_quotation => 'Order',
|
|
sales_order_intake => 'Order',
|
|
sales_order => 'Order',
|
|
request_quotation => 'Order',
|
|
purchase_quotation_intake => 'Order',
|
|
purchase_order => 'Order',
|
|
sales_delivery_order => 'DeliveryOrder',
|
|
purchase_delivery_order => 'DeliveryOrder',
|
|
sales_reclamation => 'Reclamation',
|
|
purchase_reclamation => 'Reclamation',
|
|
supplier_delivery_order => 'DeliveryOrder',
|
|
rma_delivery_order => 'DeliveryOrder',
|
|
credit_note => 'Invoice',
|
|
invoice => 'Invoice',
|
|
invoice_for_advance_payment => 'Invoice',
|
|
final_invoice => 'Invoice',
|
|
purchase_invoice => 'PurchaseInvoice',
|
|
part => 'Part',
|
|
service => 'Part',
|
|
assembly => 'Part',
|
|
letter => 'Letter',
|
|
general_ledger => 'GLTransaction',
|
|
gl_transaction => 'GLTransaction',
|
|
accounts_payable => 'GLTransaction',
|
|
shop_image => 'Part',
|
|
customer => 'Customer',
|
|
vendor => 'Vendor',
|
|
);
|
|
|
|
my %model_to_number = (
|
|
Order => 'record_number',
|
|
DeliveryOrder => 'record_number',
|
|
Reclamation => 'record_number',
|
|
Invoice => 'invnumber',
|
|
PurchaseInvoice => 'invnumber',
|
|
Part => 'partnumber',
|
|
Letter => 'letternumber',
|
|
GLTransaction => 'reference',
|
|
ShopImage => 'partnumber',
|
|
Customer => 'customernumber',
|
|
Vendor => 'vendornumber',
|
|
);
|
|
|
|
sub webdav_path {
|
|
my ($self, $dbfile) = @_;
|
|
|
|
#die "No webdav backend enabled" unless $::instance_conf->get_webdav;
|
|
|
|
my $type = $type_to_path{ $dbfile->object_type };
|
|
|
|
die "Unknown type" unless $type;
|
|
|
|
my $number = $dbfile->backend_data;
|
|
if ($number eq '') {
|
|
$number = $self->_get_number_from_model($dbfile);
|
|
$dbfile->backend_data($number);
|
|
$dbfile->save;
|
|
}
|
|
$number =~ s/\//-/g; # replace forbidden char;
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "file_name=" . $dbfile->file_name ." number=".$number);
|
|
|
|
my @fileparts = split(/_/, $dbfile->file_name);
|
|
my $number_ext = pop @fileparts;
|
|
my ($maynumber, $ext) = split(/\./, $number_ext, 2);
|
|
push @fileparts, $maynumber if $maynumber ne $number;
|
|
|
|
my $basename = join('_', @fileparts);
|
|
|
|
my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id}, $type, $number);
|
|
if (!-d $path) {
|
|
File::Path::make_path($path, { chmod => 0770 });
|
|
}
|
|
my $fname = $basename . '_' . $number . '_' . $dbfile->itime->strftime('%Y%m%d_%H%M%S');
|
|
$fname .= '.' . $ext if $ext;
|
|
|
|
$main::lxdebug->message(LXDebug->DEBUG2(), "webdav path=" . $path . " filename=" . $fname);
|
|
|
|
return (File::Spec->catfile($path, $fname), $path, $fname);
|
|
}
|
|
|
|
sub get_rootdir { SL::System::Process::exe_dir() }
|
|
|
|
sub _get_number_from_model {
|
|
my ($self, $dbfile) = @_;
|
|
|
|
my $class = 'SL::DB::' . $type_to_model{ $dbfile->object_type };
|
|
eval "require $class";
|
|
my $obj = $class->new(id => $dbfile->object_id)->load;
|
|
die 'no object found' unless $obj;
|
|
my $numberattr = $model_to_number{ $type_to_model{ $dbfile->object_type } };
|
|
return $obj->$numberattr;
|
|
}
|
|
|
|
#
|
|
# TODO not fully imlemented and tested
|
|
#
|
|
sub sync_all_locations {
|
|
my ($self, %params) = @_;
|
|
|
|
my %dateparms = (dateformat => 'yyyymmdd');
|
|
|
|
foreach my $type (keys %type_to_path) {
|
|
|
|
my @query = (
|
|
file_type => $params{file_type},
|
|
object_type => $type
|
|
);
|
|
my @oldfiles = @{ SL::DB::Manager::File->get_all(
|
|
query => [
|
|
file_type => $params{file_type},
|
|
object_type => $type
|
|
]
|
|
)
|
|
};
|
|
|
|
my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id},$type_to_path{$type});
|
|
|
|
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|.*/||;
|
|
|
|
my ($filename, $number, $date, $time_ext) = split(/_/, $fname);
|
|
my ($time, $ext) = split(/\./, $time_ext, 2);
|
|
|
|
$time = substr($time, 0, 2) . ':' . substr($time, 2, 2) . ':' . substr($time, 4, 2);
|
|
|
|
#my @found = grep { $_->backend_data eq $fname } @oldfiles;
|
|
#if (scalar(@found) > 0) {
|
|
# @oldfiles = grep { $_ != @found[0] } @oldfiles;
|
|
#}
|
|
#else {
|
|
my $dbfile = SL::DB::File->new();
|
|
my $class = 'SL::DB::Manager::' . $type_to_model{$type};
|
|
my $obj =
|
|
$class->find_by(
|
|
$model_to_number{ $type_to_model{$type} } => $number);
|
|
if ($obj) {
|
|
|
|
my $mime_type = File::MimeInfo::Magic::magic(File::Spec->catfile($path, $fname));
|
|
if (!$mime_type) {
|
|
# if filename has the suffix "pdf", but is really no pdf set mimetype for no suffix
|
|
$mime_type = File::MimeInfo::Magic::mimetype($fname);
|
|
$mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
|
|
}
|
|
|
|
$dbfile->assign_attributes(
|
|
object_id => $obj->id,
|
|
object_type => $type,
|
|
source => $params{file_type} eq 'document' ? 'created' : 'uploaded',
|
|
file_type => $params{file_type},
|
|
file_name => $filename . '_' . $number . '_' . $ext,
|
|
mime_type => $mime_type,
|
|
itime => $::locale->parse_date_to_object($date . ' ' . $time, %dateparms),
|
|
);
|
|
$dbfile->save;
|
|
}
|
|
#}
|
|
|
|
closedir $dir;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=pod
|
|
|
|
=encoding utf8
|
|
|
|
=head1 NAME
|
|
|
|
SL::File::Backend::Filesystem - Filesystem class for file storage backend
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
See the synopsis of L<SL::File::Backend>.
|
|
|
|
=head1 OVERVIEW
|
|
|
|
This specific storage backend use a Filesystem which is only accessed by this interface.
|
|
This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
|
|
This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
|
|
to store the files not to flat in the filesystem.
|
|
|
|
|
|
=head1 METHODS
|
|
|
|
See methods of L<SL::File::Backend>.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<SL::File::Backend>
|
|
|
|
=head1 TODO
|
|
|
|
The synchronization must be tested and a periodical task is needed to synchronize in some time periods.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
|
|
|
|
=cut
|