68263b88e6130f67ff66f18ba8b90e88c3334989
[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 $backend_data = $params{dbfile}->backend_data;
19   $backend_data    = 0                               if $params{last};
20   $backend_data    = $params{dbfile}->backend_data-1 if $params{all_but_notlast};
21
22   if ($backend_data > 0 ) {
23     for my $version ( 1..$backend_data) {
24       my $file_path = $self->_filesystem_path($params{dbfile},$version);
25       unlink($file_path);
26     }
27     if ($params{all_but_notlast}) {
28       my $from = $self->_filesystem_path($params{dbfile},$params{dbfile}->backend_data);
29       my $to   = $self->_filesystem_path($params{dbfile},1);
30       die "file not exists in backend delete" unless -f $from;
31       rename($from,$to);
32       $params{dbfile}->backend_data(1);
33     } else {
34       $params{dbfile}->backend_data(0);
35       my $dir_path = $self->_filesystem_path($params{dbfile});
36       rmdir($dir_path);
37     }
38   } else {
39     my $file_path = $self->_filesystem_path($params{dbfile},$params{dbfile}->backend_data);
40     die "file not exists in backend delete" unless -f $file_path;
41     unlink($file_path);
42     $params{dbfile}->backend_data($params{dbfile}->backend_data-1);
43   }
44   return 1;
45 }
46
47 sub rename {
48 }
49
50 sub save {
51   my ($self, %params) = @_;
52   die 'dbfile not exists' unless $params{dbfile};
53   my $dbfile = $params{dbfile};
54   die 'no file contents' unless $params{file_path} || $params{file_contents};
55   $dbfile->backend_data(0) unless $dbfile->backend_data;
56   $dbfile->backend_data($dbfile->backend_data*1+1);
57   $dbfile->save->load;
58
59   my $tofile = $self->_filesystem_path($dbfile);
60   if ($params{file_path} && -f $params{file_path}) {
61     File::Copy::copy($params{file_path}, $tofile);
62   }
63   elsif ($params{file_contents}) {
64     open(OUT, "> " . $tofile);
65     print OUT $params{file_contents};
66     close(OUT);
67   }
68   return 1;
69 }
70
71 sub get_version_count {
72   my ($self, %params) = @_;
73   die "no dbfile" unless $params{dbfile};
74   return $params{dbfile}->backend_data * 1;
75 }
76
77 sub get_mtime {
78   my ($self, %params) = @_;
79   die "no dbfile" unless $params{dbfile};
80   die "unknown version" if $params{version} &&
81                           ($params{version} < 0 || $params{version} > $params{dbfile}->backend_data);
82   my $path = $self->_filesystem_path($params{dbfile}, $params{version});
83
84   die "No file found at $path. Expected: $params{dbfile}{file_name}, file.id: $params{dbfile}{id}" if !-f $path;
85
86   my @st = stat($path);
87   my $dt = DateTime->from_epoch(epoch => $st[9])->clone();
88   return $dt;
89 }
90
91 sub get_filepath {
92   my ($self, %params) = @_;
93   die "no dbfile" unless $params{dbfile};
94   my $path = $self->_filesystem_path($params{dbfile},$params{version});
95   die "no file in backend get_filepath" if !-f $path;
96   return $path;
97 }
98
99 sub get_content {
100   my ($self, %params) = @_;
101   my $path = $self->get_filepath(%params);
102   return "" unless $path;
103   my $contents = File::Slurp::read_file($path);
104   return \$contents;
105 }
106
107 sub enabled {
108   return 0 unless $::instance_conf->get_doc_files;
109   return 0 unless $::lx_office_conf{paths}->{document_path};
110   return 0 unless -d $::lx_office_conf{paths}->{document_path};
111   return 1;
112 }
113
114
115 #
116 # internals
117 #
118
119 sub _filesystem_path {
120   my ($self, $dbfile, $version) = @_;
121
122   die "No files backend enabled" unless $::instance_conf->get_doc_files || $::lx_office_conf{paths}->{document_path};
123
124   # use filesystem with depth 3
125   $version    = $dbfile->backend_data if !$version || $version < 1 || $version > $dbfile->backend_data;
126   my $iddir   = sprintf("%04d", $dbfile->id % 1000);
127   my $path    = File::Spec->catdir($::lx_office_conf{paths}->{document_path}, $::auth->client->{id}, $iddir, $dbfile->id);
128   if (!-d $path) {
129     File::Path::make_path($path, { chmod => 0770 });
130   }
131   return $path if !$version;
132   return File::Spec->catdir($path, $dbfile->id . '_' . $version);
133 }
134
135 1;
136
137 __END__
138
139 =pod
140
141 =encoding utf8
142
143 =head1 NAME
144
145 SL::File::Backend::Filesystem  - Filesystem class for file storage backend
146
147 =head1 SYNOPSIS
148
149 See the synopsis of L<SL::File::Backend>.
150
151 =head1 OVERVIEW
152
153 This specific storage backend use a Filesystem which is only accessed by this interface.
154 This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
155 This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
156 to store the files not to flat in the filesystem. In this Subdirectories for each file an additional subdirectory exists
157 for the versions of this file.
158
159 The Versioning is done via a Versionnumber which is incremented by one for each version.
160 So the Version 2 of the file with the database id 4 is stored as path {root}/0004/4/4_2.
161
162
163 =head1 METHODS
164
165 See methods of L<SL::File::Backend>.
166
167 =head1 SEE ALSO
168
169 L<SL::File::Backend>
170
171 =head1 AUTHOR
172
173 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
174
175 =cut