]> wagnertech.de Git - mfinanz.git/blob - SL/File/Backend/Webdav.pm
Merge branch 'master' of http://wagnertech.de/git/mfinanz
[mfinanz.git] / SL / File / Backend / Webdav.pm
1 package SL::File::Backend::Webdav;
2
3 use strict;
4
5 use parent qw(SL::File::Backend);
6 use SL::DB::File;
7 use SL::DB::FileVersion;
8
9 use SL::System::Process;
10 use File::Copy;
11 use File::Slurp;
12 use File::Basename;
13 use File::Path qw(make_path);
14 use File::MimeInfo::Magic;
15 use File::stat;
16 use UUID::Tiny ':std';
17
18 #
19 # public methods
20 #
21
22 sub delete {
23   my ($self, %params) = @_;
24   $main::lxdebug->message(LXDebug->DEBUG2(), "del in backend " . $self . "  file " . $params{dbfile});
25   $main::lxdebug->message(LXDebug->DEBUG2(), "file id=" . $params{dbfile}->id * 1);
26   return 0 unless $params{dbfile};
27   my ($file_path, undef, undef) = $self->webdav_path($params{dbfile});
28   unlink($file_path);
29   return 1;
30 }
31
32 sub rename {
33   my ($self, %params) = @_;
34   return 0 unless $params{dbfile};
35   my (undef, $oldwebdavname) = split(/:/, $params{dbfile}->location, 2);
36   my ($tofile, $basepath, $basename) = $self->webdav_path($params{dbfile});
37   my $fromfile = File::Spec->catfile($basepath, $oldwebdavname);
38   $main::lxdebug->message(LXDebug->DEBUG2(), "renamefrom=" . $fromfile . " to=" . $tofile);
39   move($fromfile, $tofile);
40 }
41
42 sub save {
43   my ($self, %params) = @_;
44   die 'dbfile not exists' unless $params{dbfile};
45   $main::lxdebug->message(LXDebug->DEBUG2(), "in backend " . $self . "  file " . $params{dbfile});
46   $main::lxdebug->message(LXDebug->DEBUG2(), "file id=" . $params{dbfile}->id);
47   my $dbfile = $params{dbfile};
48   die 'no file contents' unless $params{file_path} || $params{file_contents};
49
50   if ($params{dbfile}->id * 1 == 0) {
51
52     # new element: need id for file
53     $params{dbfile}->save;
54   }
55   my ($tofile, undef, $basename) = $self->webdav_path($params{dbfile});
56   if ($params{file_path} && -f $params{file_path}) {
57     copy($params{file_path}, $tofile);
58   }
59   elsif ($params{file_contents}) {
60     open(OUT, "> " . $tofile);
61     print OUT $params{file_contents};
62     close(OUT);
63   }
64
65   # save file version
66   my $doc_path = $self->get_rootdir();
67   my $rel_file = $tofile;
68   $rel_file    =~ s/$doc_path//;
69   my $fv = SL::DB::FileVersion->new(
70     file_id       => $params{dbfile}->id,
71     version       => 1, # Webdav doesn't have versions by now.
72     file_location => $rel_file,
73     doc_path      => $doc_path,
74     backend       => 'Webdav',
75     guid          => create_uuid_as_string(UUID_V4),
76   )->save;
77
78   return 1;
79 }
80
81 sub get_version_count {
82   my ($self, %params) = @_;
83   die "no dbfile" unless $params{dbfile};
84   # TODO: Webdav doesn't have versions by now.
85   my ($path, undef, undef) = $self->webdav_path($params{dbfile});
86   return (-f $path || 0) * 1; # return 1 if file is found otherwise 0
87 }
88
89 sub get_mtime {
90   my ($self, %params) = @_;
91   die "no dbfile" unless $params{dbfile};
92   $main::lxdebug->message(LXDebug->DEBUG2(), "version=" .$params{version});
93   my ($path, undef, undef) = $self->webdav_path($params{dbfile});
94   die "No file found in Backend: " . $path unless -f $path;
95   my $dt = DateTime->from_epoch(epoch => stat($path)->mtime, time_zone => $::locale->get_local_time_zone()->name)->clone();
96   $main::lxdebug->message(LXDebug->DEBUG2(), "dt=" .$dt);
97   return $dt;
98 }
99
100 sub get_filepath {
101   my ($self, %params) = @_;
102   die "no dbfile" unless $params{dbfile};
103   my ($path, undef, undef) = $self->webdav_path($params{dbfile});
104   die "No file found in Backend: " . $path unless -f $path;
105   return $path;
106 }
107
108 sub get_content {
109   my ($self, %params) = @_;
110   my $path = $self->get_filepath(%params);
111   return "" unless $path;
112   my $contents = File::Slurp::read_file($path);
113   return \$contents;
114 }
115
116 sub sync_from_backend {
117   my ($self, %params) = @_;
118   return unless $params{file_type};
119
120   $self->sync_all_locations(%params);
121
122 }
123
124 sub enabled {
125   return $::instance_conf->get_doc_webdav;
126 }
127
128 #
129 # internals
130 #
131
132 my %type_to_path = (
133   sales_quotation             => 'angebote',
134   sales_order_intake          => 'auftragseingaenge',
135   sales_order                 => 'bestellungen',
136   request_quotation           => 'anfragen',
137   purchase_quotation_intake   => 'angebotseingaenge',
138   purchase_order              => 'lieferantenbestellungen',
139   purchase_order_confirmation => 'lieferantenauftragsbestaetigungen',
140   sales_delivery_order        => 'verkaufslieferscheine',
141   purchase_delivery_order     => 'einkaufslieferscheine',
142   purchase_reclamation        => 'einkaufsreklamation',
143   sales_reclamation           => 'verkaufsreklamation',
144   supplier_delivery_order     => 'beistelllieferscheine',
145   rma_delivery_order          => 'retourenlieferscheine',
146   credit_note                 => 'gutschriften',
147   invoice                     => 'rechnungen',
148   invoice_for_advance_payment => 'rechnungen',
149   final_invoice               => 'rechnungen',
150   purchase_invoice            => 'einkaufsrechnungen',
151   part                        => 'waren',
152   service                     => 'dienstleistungen',
153   assembly                    => 'erzeugnisse',
154   letter                      => 'briefe',
155   general_ledger              => 'dialogbuchungen',
156   gl_transaction              => 'dialogbuchungen',
157   accounts_payable            => 'kreditorenbuchungen',
158   shop_image                  => 'shopbilder',
159   customer                    => 'kunden',
160   vendor                      => 'lieferanten',
161 );
162
163 my %type_to_model = (
164   sales_quotation             => 'Order',
165   sales_order_intake          => 'Order',
166   sales_order                 => 'Order',
167   request_quotation           => 'Order',
168   purchase_quotation_intake   => 'Order',
169   purchase_order              => 'Order',
170   sales_delivery_order        => 'DeliveryOrder',
171   purchase_delivery_order     => 'DeliveryOrder',
172   sales_reclamation           => 'Reclamation',
173   purchase_reclamation        => 'Reclamation',
174   supplier_delivery_order     => 'DeliveryOrder',
175   rma_delivery_order          => 'DeliveryOrder',
176   credit_note                 => 'Invoice',
177   invoice                     => 'Invoice',
178   invoice_for_advance_payment => 'Invoice',
179   final_invoice               => 'Invoice',
180   purchase_invoice            => 'PurchaseInvoice',
181   part                        => 'Part',
182   service                     => 'Part',
183   assembly                    => 'Part',
184   letter                      => 'Letter',
185   general_ledger              => 'GLTransaction',
186   gl_transaction              => 'GLTransaction',
187   accounts_payable            => 'GLTransaction',
188   shop_image                  => 'Part',
189   customer                    => 'Customer',
190   vendor                      => 'Vendor',
191 );
192
193 my %model_to_number = (
194   Order           => 'record_number',
195   DeliveryOrder   => 'record_number',
196   Reclamation     => 'record_number',
197   Invoice         => 'invnumber',
198   PurchaseInvoice => 'invnumber',
199   Part            => 'partnumber',
200   Letter          => 'letternumber',
201   GLTransaction   => 'reference',
202   ShopImage       => 'partnumber',
203   Customer        => 'customernumber',
204   Vendor          => 'vendornumber',
205 );
206
207 sub webdav_path {
208   my ($self, $dbfile) = @_;
209
210   #die "No webdav backend enabled" unless $::instance_conf->get_webdav;
211
212   my $type = $type_to_path{ $dbfile->object_type };
213
214   die "Unknown type" unless $type;
215
216   my $number = $dbfile->backend_data;
217   if ($number eq '') {
218     $number = $self->_get_number_from_model($dbfile);
219     $dbfile->backend_data($number);
220     $dbfile->save;
221   }
222   $number =~ s/\//-/g; # replace forbidden char;
223   $main::lxdebug->message(LXDebug->DEBUG2(), "file_name=" . $dbfile->file_name ." number=".$number);
224
225   my @fileparts = split(/_/, $dbfile->file_name);
226   my $number_ext = pop @fileparts;
227   my ($maynumber, $ext) = split(/\./, $number_ext, 2);
228   push @fileparts, $maynumber if $maynumber ne $number;
229
230   my $basename = join('_', @fileparts);
231
232   my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id}, $type, $number);
233   if (!-d $path) {
234     File::Path::make_path($path, { chmod => 0770 });
235   }
236   my $fname = $basename . '_' . $number . '_' . $dbfile->itime->strftime('%Y%m%d_%H%M%S');
237   $fname .= '.' . $ext if $ext;
238
239   $main::lxdebug->message(LXDebug->DEBUG2(), "webdav path=" . $path . " filename=" . $fname);
240
241   return (File::Spec->catfile($path, $fname), $path, $fname);
242 }
243
244 sub get_rootdir { SL::System::Process::exe_dir() }
245
246 sub _get_number_from_model {
247   my ($self, $dbfile) = @_;
248
249   my $class = 'SL::DB::' . $type_to_model{ $dbfile->object_type };
250   eval "require $class";
251   my $obj = $class->new(id => $dbfile->object_id)->load;
252   die 'no object found' unless $obj;
253   my $numberattr = $model_to_number{ $type_to_model{ $dbfile->object_type } };
254   return $obj->$numberattr;
255 }
256
257 #
258 # TODO not fully imlemented and tested
259 #
260 sub sync_all_locations {
261   my ($self, %params) = @_;
262
263   my %dateparms = (dateformat => 'yyyymmdd');
264
265   foreach my $type (keys %type_to_path) {
266
267     my @query = (
268       file_type => $params{file_type},
269       object_type    => $type
270     );
271     my @oldfiles = @{ SL::DB::Manager::File->get_all(
272         query => [
273           file_type => $params{file_type},
274           object_type    => $type
275         ]
276       )
277     };
278
279     my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id},$type_to_path{$type});
280
281     if (opendir my $dir, $path) {
282       foreach my $file (sort { lc $a cmp lc $b }
283         map { decode("UTF-8", $_) } readdir $dir)
284       {
285         next if (($file eq '.') || ($file eq '..'));
286
287         my $fname = $file;
288         $fname =~ s|.*/||;
289
290         my ($filename, $number, $date, $time_ext) = split(/_/, $fname);
291         my ($time, $ext) = split(/\./, $time_ext, 2);
292
293         $time = substr($time, 0, 2) . ':' . substr($time, 2, 2) . ':' . substr($time, 4, 2);
294
295         #my @found = grep { $_->backend_data eq $fname } @oldfiles;
296         #if (scalar(@found) > 0) {
297         #  @oldfiles = grep { $_ != @found[0] } @oldfiles;
298         #}
299         #else {
300           my $dbfile = SL::DB::File->new();
301           my $class  = 'SL::DB::Manager::' . $type_to_model{$type};
302           my $obj =
303             $class->find_by(
304             $model_to_number{ $type_to_model{$type} } => $number);
305           if ($obj) {
306
307             my $mime_type = File::MimeInfo::Magic::magic(File::Spec->catfile($path, $fname));
308             if (!$mime_type) {
309               # if filename has the suffix "pdf", but is really no pdf set mimetype for no suffix
310               $mime_type = File::MimeInfo::Magic::mimetype($fname);
311               $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
312             }
313
314             $dbfile->assign_attributes(
315               object_id   => $obj->id,
316               object_type => $type,
317               source      => $params{file_type} eq 'document' ? 'created' : 'uploaded',
318               file_type   => $params{file_type},
319               file_name   => $filename . '_' . $number . '_' . $ext,
320               mime_type   => $mime_type,
321               itime       => $::locale->parse_date_to_object($date . ' ' . $time, %dateparms),
322             );
323             $dbfile->save;
324           }
325         #}
326
327         closedir $dir;
328       }
329     }
330   }
331 }
332
333 1;
334
335 __END__
336
337 =pod
338
339 =encoding utf8
340
341 =head1 NAME
342
343 SL::File::Backend::Filesystem  - Filesystem class for file storage backend
344
345 =head1 SYNOPSIS
346
347 See the synopsis of L<SL::File::Backend>.
348
349 =head1 OVERVIEW
350
351 This specific storage backend use a Filesystem which is only accessed by this interface.
352 This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
353 This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
354 to store the files not to flat in the filesystem.
355
356
357 =head1 METHODS
358
359 See methods of L<SL::File::Backend>.
360
361 =head1 SEE ALSO
362
363 L<SL::File::Backend>
364
365 =head1 TODO
366
367 The synchronization must be tested and a periodical task is needed to synchronize in some time periods.
368
369 =head1 AUTHOR
370
371 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
372
373 =cut