Dateimanagement: lokalisiertes Datum in Darstellung
[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   return 1;
83 }
84
85 sub get_version_count {
86   my ($self, %params) = @_;
87   die "no dbfile" unless $params{dbfile};
88   return $params{dbfile}->backend_data * 1;
89 }
90
91 sub get_mtime {
92   my ($self, %params) = @_;
93   die "no dbfile" unless $params{dbfile};
94   die "unknown version" if $params{version} &&
95                           ($params{version} < 0 || $params{version} > $params{dbfile}->backend_data);
96   my $path = $self->_filesystem_path($params{dbfile}, $params{version});
97
98   die "No file found at $path. Expected: $params{dbfile}{file_name}, file.id: $params{dbfile}{id}" if !-f $path;
99
100   my @st = stat($path);
101   my $dt = DateTime->from_epoch(epoch => $st[9], time_zone => $::locale->get_local_time_zone()->name, locale => $::lx_office_conf{system}->{language})->clone();
102   return $dt;
103 }
104
105 sub get_filepath {
106   my ($self, %params) = @_;
107   die "no dbfile" unless $params{dbfile};
108   my $path = $self->_filesystem_path($params{dbfile},$params{version});
109   die "no file in backend get_filepath" if !-f $path;
110   return $path;
111 }
112
113 sub get_content {
114   my ($self, %params) = @_;
115   my $path = $self->get_filepath(%params);
116   return "" unless $path;
117   my $contents = File::Slurp::read_file($path);
118   return \$contents;
119 }
120
121 sub enabled {
122   return 0 unless $::instance_conf->get_doc_files;
123   return 0 unless $::lx_office_conf{paths}->{document_path};
124   return 0 unless -d $::lx_office_conf{paths}->{document_path};
125   return 1;
126 }
127
128 sub sync_from_backend {
129   my ($self, %params) = @_;
130   my @query = (file_type => $params{file_type});
131   push @query, (file_name => $params{file_name}) if $params{file_name};
132   push @query, (mime_type => $params{mime_type}) if $params{mime_type};
133   push @query, (source    => $params{source})    if $params{source};
134
135   my $sortby = $params{sort_by} || 'itime DESC,file_name ASC';
136
137   my @files = @{ SL::DB::Manager::File->get_all(query => [@query], sort_by => $sortby) };
138   for (@files) {
139     $main::lxdebug->message(LXDebug->DEBUG2(), "file id=" . $_->id." version=".$_->backend_data);
140     my $newversion = $_->backend_data;
141     for my $version ( reverse 1 .. $_->backend_data ) {
142       my $path = $self->_filesystem_path($_, $version);
143       $main::lxdebug->message(LXDebug->DEBUG2(), "path=".$path." exists=".( -f $path?1:0));
144       last if -f $path;
145       $newversion = $version - 1;
146     }
147     $main::lxdebug->message(LXDebug->DEBUG2(), "newversion=".$newversion." version=".$_->backend_data);
148     if ( $newversion < $_->backend_data ) {
149       $_->backend_data($newversion);
150       $_->save   if $newversion >  0;
151       $_->delete if $newversion <= 0;
152     }
153   }
154
155 }
156
157 #
158 # internals
159 #
160
161 sub _filesystem_path {
162   my ($self, $dbfile, $version) = @_;
163
164   die "No files backend enabled" unless $::instance_conf->get_doc_files || $::lx_office_conf{paths}->{document_path};
165
166   # use filesystem with depth 3
167   $version    = $dbfile->backend_data if !$version || $version < 1 || $version > $dbfile->backend_data;
168   my $iddir   = sprintf("%04d", $dbfile->id % 1000);
169   my $path    = File::Spec->catdir($::lx_office_conf{paths}->{document_path}, $::auth->client->{id}, $iddir, $dbfile->id);
170   if (!-d $path) {
171     File::Path::make_path($path, { chmod => 0770 });
172   }
173   return $path if !$version;
174   return File::Spec->catdir($path, $dbfile->id . '_' . $version);
175 }
176
177 1;
178
179 __END__
180
181 =pod
182
183 =encoding utf8
184
185 =head1 NAME
186
187 SL::File::Backend::Filesystem  - Filesystem class for file storage backend
188
189 =head1 SYNOPSIS
190
191 See the synopsis of L<SL::File::Backend>.
192
193 =head1 OVERVIEW
194
195 This specific storage backend use a Filesystem which is only accessed by this interface.
196 This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
197 This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
198 to store the files not to flat in the filesystem. In this Subdirectories for each file an additional subdirectory exists
199 for the versions of this file.
200
201 The Versioning is done via a Versionnumber which is incremented by one for each version.
202 So the Version 2 of the file with the database id 4 is stored as path {root}/0004/4/4_2.
203
204
205 =head1 METHODS
206
207 See methods of L<SL::File::Backend>.
208
209 =head1 SEE ALSO
210
211 L<SL::File::Backend>
212
213 =head1 AUTHOR
214
215 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
216
217 =cut