|  | package SL::File::Backend::Webdav;
 | 
  
    |  | 
 | 
  
    |  | use strict;
 | 
  
    |  | 
 | 
  
    |  | use parent qw(SL::File::Backend);
 | 
  
    |  | use SL::DB::File;
 | 
  
    |  | use SL::DB::FileVersion;
 | 
  
    |  | 
 | 
  
    |  | 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;
 | 
  
    |  | use UUID::Tiny ':std';
 | 
  
    |  | 
 | 
  
    |  | #
 | 
  
    |  | # 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);
 | 
  
    |  |   }
 | 
  
    |  | 
 | 
  
    |  |   # save file version
 | 
  
    |  |   my $doc_path = $self->get_rootdir();
 | 
  
    |  |   my $rel_file = $tofile;
 | 
  
    |  |   $rel_file    =~ s/$doc_path//;
 | 
  
    |  |   my $fv = SL::DB::FileVersion->new(
 | 
  
    |  |     file_id       => $params{dbfile}->id,
 | 
  
    |  |     version       => 1, # Webdav doesn't have versions by now.
 | 
  
    |  |     file_location => $rel_file,
 | 
  
    |  |     doc_path      => $doc_path,
 | 
  
    |  |     backend       => 'Webdav',
 | 
  
    |  |     guid          => create_uuid_as_string(UUID_V4),
 | 
  
    |  |   )->save;
 | 
  
    |  | 
 | 
  
    |  |   return 1;
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | sub get_version_count {
 | 
  
    |  |   my ($self, %params) = @_;
 | 
  
    |  |   die "no dbfile" unless $params{dbfile};
 | 
  
    |  |   # TODO: Webdav doesn't have versions by now.
 | 
  
    |  |   my ($path, undef, undef) = $self->webdav_path($params{dbfile});
 | 
  
    |  |   return (-f $path || 0) * 1; # return 1 if file is found otherwise 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',
 | 
  
    |  |   purchase_order_confirmation => 'lieferantenauftragsbestaetigungen',
 | 
  
    |  |   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
 |