Dateimanagment: itime/mtime in DB kann gesetzt werden, mtime kann in Filesystem-Backe...
[kivitendo-erp.git] / SL / File / Backend / Filesystem.pm
1 package SL::File::Backend::Filesystem;
2
3 use strict;
4
5 use parent qw(SL::File::Backend);
6 use SL::DB::File;
7 use File::Copy;
8 use File::Slurp;
9 use File::Path qw(make_path);
10
11 #
12 # public methods
13 #
14
15 sub delete {
16   my ($self, %params) = @_;
17   die "no dbfile in backend delete" unless $params{dbfile};
18   my $last_version  = $params{dbfile}->backend_data;
19   my $first_version = 1;
20   $last_version     = 0                               if $params{last};
21   $last_version     = $params{dbfile}->backend_data-1 if $params{all_but_notlast};
22   $last_version     = $params{version}                if $params{version};
23   $first_version    = $params{version}                if $params{version};
24
25   if ($last_version > 0 ) {
26     for my $version ( $first_version..$last_version) {
27       my $file_path = $self->_filesystem_path($params{dbfile},$version);
28       unlink($file_path);
29     }
30     if ($params{version}) {
31       for my $version ( $last_version+1 .. $params{dbfile}->backend_data) {
32         my $from = $self->_filesystem_path($params{dbfile},$version);
33         my $to   = $self->_filesystem_path($params{dbfile},$version - 1);
34         die "file not exists in backend delete" unless -f $from;
35         rename($from,$to);
36       }
37       $params{dbfile}->backend_data($params{dbfile}->backend_data-1);
38     }
39     elsif ($params{all_but_notlast}) {
40       my $from = $self->_filesystem_path($params{dbfile},$params{dbfile}->backend_data);
41       my $to   = $self->_filesystem_path($params{dbfile},1);
42       die "file not exists in backend delete" unless -f $from;
43       rename($from,$to);
44       $params{dbfile}->backend_data(1);
45     } else {
46       $params{dbfile}->backend_data(0);
47     }
48     unless ($params{dbfile}->backend_data) {
49       my $dir_path = $self->_filesystem_path($params{dbfile});
50       rmdir($dir_path);
51     }
52   } else {
53     my $file_path = $self->_filesystem_path($params{dbfile},$params{dbfile}->backend_data);
54     die "file not exists in backend delete" unless -f $file_path;
55     unlink($file_path);
56     $params{dbfile}->backend_data($params{dbfile}->backend_data-1);
57   }
58   return 1;
59 }
60
61 sub rename {
62 }
63
64 sub save {
65   my ($self, %params) = @_;
66   die 'dbfile not exists' unless $params{dbfile};
67   my $dbfile = $params{dbfile};
68   die 'no file contents' unless $params{file_path} || $params{file_contents};
69   $dbfile->backend_data(0) unless $dbfile->backend_data;
70   $dbfile->backend_data($dbfile->backend_data*1+1);
71   $dbfile->save->load;
72
73   my $tofile = $self->_filesystem_path($dbfile);
74   if ($params{file_path} && -f $params{file_path}) {
75     File::Copy::copy($params{file_path}, $tofile);
76   }
77   elsif ($params{file_contents}) {
78     open(OUT, "> " . $tofile);
79     print OUT $params{file_contents};
80     close(OUT);
81   }
82   if ($params{mtime}) {
83     utime($params{mtime}, $params{mtime}, $tofile);
84   }
85   return 1;
86 }
87
88 sub get_version_count {
89   my ($self, %params) = @_;
90   die "no dbfile" unless $params{dbfile};
91   return $params{dbfile}->backend_data * 1;
92 }
93
94 sub get_mtime {
95   my ($self, %params) = @_;
96   die "no dbfile" unless $params{dbfile};
97   die "unknown version" if $params{version} &&
98                           ($params{version} < 0 || $params{version} > $params{dbfile}->backend_data);
99   my $path = $self->_filesystem_path($params{dbfile}, $params{version});
100
101   die "No file found at $path. Expected: $params{dbfile}{file_name}, file.id: $params{dbfile}{id}" if !-f $path;
102
103   my @st = stat($path);
104   my $dt = DateTime->from_epoch(epoch => $st[9], time_zone => $::locale->get_local_time_zone()->name, locale => $::lx_office_conf{system}->{language})->clone();
105   return $dt;
106 }
107
108 sub get_filepath {
109   my ($self, %params) = @_;
110   die "no dbfile" unless $params{dbfile};
111   my $path = $self->_filesystem_path($params{dbfile},$params{version});
112   die "no file in backend get_filepath" if !-f $path;
113   return $path;
114 }
115
116 sub get_content {
117   my ($self, %params) = @_;
118   my $path = $self->get_filepath(%params);
119   return "" unless $path;
120   my $contents = File::Slurp::read_file($path);
121   return \$contents;
122 }
123
124 sub enabled {
125   return 0 unless $::instance_conf->get_doc_files;
126   return 0 unless $::lx_office_conf{paths}->{document_path};
127   return 0 unless -d $::lx_office_conf{paths}->{document_path};
128   return 1;
129 }
130
131 sub sync_from_backend {
132   my ($self, %params) = @_;
133   my @query = (file_type => $params{file_type});
134   push @query, (file_name => $params{file_name}) if $params{file_name};
135   push @query, (mime_type => $params{mime_type}) if $params{mime_type};
136   push @query, (source    => $params{source})    if $params{source};
137
138   my $sortby = $params{sort_by} || 'itime DESC,file_name ASC';
139
140   my @files = @{ SL::DB::Manager::File->get_all(query => [@query], sort_by => $sortby) };
141   for (@files) {
142     $main::lxdebug->message(LXDebug->DEBUG2(), "file id=" . $_->id." version=".$_->backend_data);
143     my $newversion = $_->backend_data;
144     for my $version ( reverse 1 .. $_->backend_data ) {
145       my $path = $self->_filesystem_path($_, $version);
146       $main::lxdebug->message(LXDebug->DEBUG2(), "path=".$path." exists=".( -f $path?1:0));
147       last if -f $path;
148       $newversion = $version - 1;
149     }
150     $main::lxdebug->message(LXDebug->DEBUG2(), "newversion=".$newversion." version=".$_->backend_data);
151     if ( $newversion < $_->backend_data ) {
152       $_->backend_data($newversion);
153       $_->save   if $newversion >  0;
154       $_->delete if $newversion <= 0;
155     }
156   }
157
158 }
159
160 #
161 # internals
162 #
163
164 sub _filesystem_path {
165   my ($self, $dbfile, $version) = @_;
166
167   die "No files backend enabled" unless $::instance_conf->get_doc_files || $::lx_office_conf{paths}->{document_path};
168
169   # use filesystem with depth 3
170   $version    = $dbfile->backend_data if !$version || $version < 1 || $version > $dbfile->backend_data;
171   my $iddir   = sprintf("%04d", $dbfile->id % 1000);
172   my $path    = File::Spec->catdir($::lx_office_conf{paths}->{document_path}, $::auth->client->{id}, $iddir, $dbfile->id);
173   if (!-d $path) {
174     File::Path::make_path($path, { chmod => 0770 });
175   }
176   return $path if !$version;
177   return File::Spec->catdir($path, $dbfile->id . '_' . $version);
178 }
179
180 1;
181
182 __END__
183
184 =pod
185
186 =encoding utf8
187
188 =head1 NAME
189
190 SL::File::Backend::Filesystem  - Filesystem class for file storage backend
191
192 =head1 SYNOPSIS
193
194 See the synopsis of L<SL::File::Backend>.
195
196 =head1 OVERVIEW
197
198 This specific storage backend use a Filesystem which is only accessed by this interface.
199 This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
200 This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
201 to store the files not to flat in the filesystem. In this Subdirectories for each file an additional subdirectory exists
202 for the versions of this file.
203
204 The Versioning is done via a Versionnumber which is incremented by one for each version.
205 So the Version 2 of the file with the database id 4 is stored as path {root}/0004/4/4_2.
206
207
208 =head1 METHODS
209
210 See methods of L<SL::File::Backend>.
211
212 =head1 SEE ALSO
213
214 L<SL::File::Backend>
215
216 =head1 AUTHOR
217
218 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
219
220 =cut