DMS: Webdav-Backend: Root-Pfad nicht erraten, sondern holen
[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 SL::Webdav;
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
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 @st = stat($path);
79   my $dt = DateTime->from_epoch(epoch => $st[9])->clone();
80   $main::lxdebug->message(LXDebug->DEBUG2(), "dt=" .$dt);
81   return $dt;
82 }
83
84 sub get_filepath {
85   my ($self, %params) = @_;
86   die "no dbfile" unless $params{dbfile};
87   my ($path, undef, undef) = $self->webdav_path($params{dbfile});
88   die "No file found in Backend: " . $path unless -f $path;
89   return $path;
90 }
91
92 sub get_content {
93   my ($self, %params) = @_;
94   my $path = $self->get_filepath(%params);
95   return "" unless $path;
96   my $contents = File::Slurp::read_file($path);
97   return \$contents;
98 }
99
100 sub sync_from_backend {
101   my ($self, %params) = @_;
102   return unless $params{file_type};
103
104   $self->sync_all_locations(%params);
105
106 }
107
108 sub enabled {
109   return $::instance_conf->get_doc_webdav;
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 { SL::System::Process::exe_dir() }
210
211 sub _get_number_from_model {
212   my ($self, $dbfile) = @_;
213
214   my $class = 'SL::DB::' . $type_to_model{ $dbfile->object_type };
215   eval "require $class";
216   my $obj = $class->new(id => $dbfile->object_id)->load;
217   die 'no object found' unless $obj;
218   my $numberattr = $model_to_number{ $type_to_model{ $dbfile->object_type } };
219   return $obj->$numberattr;
220 }
221
222 #
223 # TODO not fully imlemented and tested
224 #
225 sub sync_all_locations {
226   my ($self, %params) = @_;
227
228   my %dateparms = (dateformat => 'yyyymmdd');
229
230   foreach my $type (keys %type_to_path) {
231
232     my @query = (
233       file_type => $params{file_type},
234       object_type    => $type
235     );
236     my @oldfiles = @{ SL::DB::Manager::File->get_all(
237         query => [
238           file_type => $params{file_type},
239           object_type    => $type
240         ]
241       )
242     };
243
244     my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id},$type_to_path{$type});
245
246     if (opendir my $dir, $path) {
247       foreach my $file (sort { lc $a cmp lc $b }
248         map { decode("UTF-8", $_) } readdir $dir)
249       {
250         next if (($file eq '.') || ($file eq '..'));
251
252         my $fname = $file;
253         $fname =~ s|.*/||;
254
255         my ($filename, $number, $date, $time_ext) = split(/_/, $fname);
256         my ($time, $ext) = split(/\./, $time_ext, 2);
257
258         $time = substr($time, 0, 2) . ':' . substr($time, 2, 2) . ':' . substr($time, 4, 2);
259
260         #my @found = grep { $_->backend_data eq $fname } @oldfiles;
261         #if (scalar(@found) > 0) {
262         #  @oldfiles = grep { $_ != @found[0] } @oldfiles;
263         #}
264         #else {
265           my $dbfile = SL::DB::File->new();
266           my $class  = 'SL::DB::Manager::' . $type_to_model{$type};
267           my $obj =
268             $class->find_by(
269             $model_to_number{ $type_to_model{$type} } => $number);
270           if ($obj) {
271
272             my $mime_type = File::MimeInfo::Magic::magic(File::Spec->catfile($path, $fname));
273             if (!$mime_type) {
274               # if filename has the suffix "pdf", but is really no pdf set mimetype for no suffix
275               $mime_type = File::MimeInfo::Magic::mimetype($fname);
276               $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
277             }
278
279             $dbfile->assign_attributes(
280               object_id   => $obj->id,
281               object_type => $type,
282               source      => $params{file_type} eq 'document' ? 'created' : 'uploaded',
283               file_type   => $params{file_type},
284               file_name   => $filename . '_' . $number . '_' . $ext,
285               mime_type   => $mime_type,
286               itime       => $::locale->parse_date_to_object($date . ' ' . $time, %dateparms),
287             );
288             $dbfile->save;
289           }
290         #}
291
292         closedir $dir;
293       }
294     }
295   }
296 }
297
298 1;
299
300 __END__
301
302 =pod
303
304 =encoding utf8
305
306 =head1 NAME
307
308 SL::File::Backend::Filesystem  - Filesystem class for file storage backend
309
310 =head1 SYNOPSIS
311
312 See the synopsis of L<SL::File::Backend>.
313
314 =head1 OVERVIEW
315
316 This specific storage backend use a Filesystem which is only accessed by this interface.
317 This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
318 This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
319 to store the files not to flat in the filesystem.
320
321
322 =head1 METHODS
323
324 See methods of L<SL::File::Backend>.
325
326 =head1 SEE ALSO
327
328 L<SL::File::Backend>
329
330 =head1 TODO
331
332 The synchronization must be tested and a periodical task is needed to synchronize in some time periods.
333
334 =head1 AUTHOR
335
336 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
337
338 =cut