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