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