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