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