871b61f5896a04ffeea833d7fd72a5f9f7f9f7ba
[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 );
135
136 my %type_to_model = (
137   sales_quotation         => 'Order',
138   sales_order             => 'Order',
139   request_quotation       => 'Order',
140   purchase_order          => 'Order',
141   sales_delivery_order    => 'DeliveryOrder',
142   purchase_delivery_order => 'DeliveryOrder',
143   credit_note             => 'Invoice',
144   invoice                 => 'Invoice',
145   purchase_invoice        => 'PurchaseInvoice',
146   part                    => 'Part',
147   service                 => 'Part',
148   assembly                => 'Part',
149   letter                  => 'Letter',
150   general_ledger          => 'GLTransaction',
151   gl_transaction          => 'GLTransaction',
152   accounts_payable        => 'GLTransaction',
153   shop_image              => 'Part',
154 );
155
156 my %model_to_number = (
157   Order           => 'ordnumber',
158   DeliveryOrder   => 'ordnumber',
159   Invoice         => 'invnumber',
160   PurchaseInvoice => 'invnumber',
161   Part            => 'partnumber',
162   Letter          => 'letternumber',
163   GLTransaction   => 'reference',
164   ShopImage       => 'partnumber',
165 );
166
167 sub webdav_path {
168   my ($self, $dbfile) = @_;
169
170   #die "No webdav backend enabled" unless $::instance_conf->get_webdav;
171
172   my $type = $type_to_path{ $dbfile->object_type };
173
174   die "Unknown type" unless $type;
175
176   my $number = $dbfile->backend_data;
177   if ($number eq '') {
178     $number = $self->_get_number_from_model($dbfile);
179     $dbfile->backend_data($number);
180     $dbfile->save;
181   }
182   $main::lxdebug->message(LXDebug->DEBUG2(), "file_name=" . $dbfile->file_name ." number=".$number);
183
184   my @fileparts = split(/_/, $dbfile->file_name);
185   my $number_ext = pop @fileparts;
186   my ($maynumber, $ext) = split(/\./, $number_ext, 2);
187   push @fileparts, $maynumber if $maynumber ne $number;
188
189   my $basename = join('_', @fileparts);
190
191   my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id}, $type, $number);
192   if (!-d $path) {
193     File::Path::make_path($path, { chmod => 0770 });
194   }
195   my $fname = $basename . '_' . $number . '_' . $dbfile->itime->strftime('%Y%m%d_%H%M%S');
196   $fname .= '.' . $ext if $ext;
197
198   $main::lxdebug->message(LXDebug->DEBUG2(), "webdav path=" . $path . " filename=" . $fname);
199
200   return (File::Spec->catfile($path, $fname), $path, $fname);
201 }
202
203 sub get_rootdir {
204   my ($self) = @_;
205
206   #TODO immer noch das alte Problem:
207   #je nachdem von woher der Aufruf kommt ist man in ./users oder .
208   my $rootdir  = POSIX::getcwd();
209   my $basename = basename($rootdir);
210   my $dirname  = dirname($rootdir);
211   $rootdir = $dirname if $basename eq 'users';
212   return $rootdir;
213 }
214
215 sub _get_number_from_model {
216   my ($self, $dbfile) = @_;
217
218   my $class = 'SL::DB::' . $type_to_model{ $dbfile->object_type };
219   eval "require $class";
220   my $obj = $class->new(id => $dbfile->object_id)->load;
221   die 'no object found' unless $obj;
222   my $numberattr = $model_to_number{ $type_to_model{ $dbfile->object_type } };
223   return $obj->$numberattr;
224 }
225
226 #
227 # TODO not fully imlemented and tested
228 #
229 sub sync_all_locations {
230   my ($self, %params) = @_;
231
232   my %dateparms = (dateformat => 'yyyymmdd');
233
234   foreach my $type (keys %type_to_path) {
235
236     my @query = (
237       file_type => $params{file_type},
238       object_type    => $type
239     );
240     my @oldfiles = @{ SL::DB::Manager::File->get_all(
241         query => [
242           file_type => $params{file_type},
243           object_type    => $type
244         ]
245       )
246     };
247
248     my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id},$type_to_path{$type});
249
250     if (opendir my $dir, $path) {
251       foreach my $file (sort { lc $a cmp lc $b }
252         map { decode("UTF-8", $_) } readdir $dir)
253       {
254         next if (($file eq '.') || ($file eq '..'));
255
256         my $fname = $file;
257         $fname =~ s|.*/||;
258
259         my ($filename, $number, $date, $time_ext) = split(/_/, $fname);
260         my ($time, $ext) = split(/\./, $time_ext, 2);
261
262         $time = substr($time, 0, 2) . ':' . substr($time, 2, 2) . ':' . substr($time, 4, 2);
263
264         #my @found = grep { $_->backend_data eq $fname } @oldfiles;
265         #if (scalar(@found) > 0) {
266         #  @oldfiles = grep { $_ != @found[0] } @oldfiles;
267         #}
268         #else {
269           my $dbfile = SL::DB::File->new();
270           my $class  = 'SL::DB::Manager::' . $type_to_model{$type};
271           my $obj =
272             $class->find_by(
273             $model_to_number{ $type_to_model{$type} } => $number);
274           if ($obj) {
275
276             my $mime_type = File::MimeInfo::Magic::magic(File::Spec->catfile($path, $fname));
277             if (!$mime_type) {
278               # if filename has the suffix "pdf", but is really no pdf set mimetype for no suffix
279               $mime_type = File::MimeInfo::Magic::mimetype($fname);
280               $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
281             }
282
283             $dbfile->assign_attributes(
284               object_id   => $obj->id,
285               object_type => $type,
286               source      => $params{file_type} eq 'document' ? 'created' : 'uploaded',
287               file_type   => $params{file_type},
288               file_name   => $filename . '_' . $number . '_' . $ext,
289               mime_type   => $mime_type,
290               itime       => $::locale->parse_date_to_object($date . ' ' . $time, %dateparms),
291             );
292             $dbfile->save;
293           }
294         #}
295
296         closedir $dir;
297       }
298     }
299   }
300 }
301
302 1;
303
304 __END__
305
306 =pod
307
308 =encoding utf8
309
310 =head1 NAME
311
312 SL::File::Backend::Filesystem  - Filesystem class for file storage backend
313
314 =head1 SYNOPSIS
315
316 See the synopsis of L<SL::File::Backend>.
317
318 =head1 OVERVIEW
319
320 This specific storage backend use a Filesystem which is only accessed by this interface.
321 This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
322 This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
323 to store the files not to flat in the filesystem.
324
325
326 =head1 METHODS
327
328 See methods of L<SL::File::Backend>.
329
330 =head1 SEE ALSO
331
332 L<SL::File::Backend>
333
334 =head1 TODO
335
336 The synchronization must be tested and a periodical task is needed to synchronize in some time periods.
337
338 =head1 AUTHOR
339
340 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
341
342 =cut
343
344