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