fe8604c0be6163bc2940513808a92c9944614a47
[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   die "no file found in backend get_mtime" if !-f $path;
84   my @st = stat($path);
85   my $dt = DateTime->from_epoch(epoch => $st[9])->clone();
86   return $dt;
87 }
88
89 sub get_filepath {
90   my ($self, %params) = @_;
91   die "no dbfile" unless $params{dbfile};
92   my $path = $self->_filesystem_path($params{dbfile},$params{version});
93   die "no file in backend get_filepath" if !-f $path;
94   return $path;
95 }
96
97 sub get_content {
98   my ($self, %params) = @_;
99   my $path = $self->get_filepath(%params);
100   return "" unless $path;
101   my $contents = File::Slurp::read_file($path);
102   return \$contents;
103 }
104
105 sub enabled {
106   return 0 unless $::instance_conf->get_doc_files;
107   return 0 unless $::lx_office_conf{paths}->{document_path};
108   return 0 unless -d $::lx_office_conf{paths}->{document_path};
109   return 1;
110 }
111
112
113 #
114 # internals
115 #
116
117 sub _filesystem_path {
118   my ($self, $dbfile, $version) = @_;
119
120   die "No files backend enabled" unless $::instance_conf->get_doc_files || $::lx_office_conf{paths}->{document_path};
121
122   # use filesystem with depth 3
123   $version    = $dbfile->backend_data if !$version || $version < 1 || $version > $dbfile->backend_data;
124   my $iddir   = sprintf("%04d", $dbfile->id % 1000);
125   my $path    = File::Spec->catdir($::lx_office_conf{paths}->{document_path}, $::auth->client->{id}, $iddir, $dbfile->id);
126   if (!-d $path) {
127     File::Path::make_path($path, { chmod => 0770 });
128   }
129   return $path if !$version;
130   return File::Spec->catdir($path, $dbfile->id . '_' . $version);
131 }
132
133 1;
134
135 __END__
136
137 =pod
138
139 =encoding utf8
140
141 =head1 NAME
142
143 SL::File::Backend::Filesystem  - Filesystem class for file storage backend
144
145 =head1 SYNOPSIS
146
147 See the synopsis of L<SL::File::Backend>.
148
149 =head1 OVERVIEW
150
151 This specific storage backend use a Filesystem which is only accessed by this interface.
152 This is the big difference to the Webdav backend where the files can be accessed without the control of that backend.
153 This backend use the database id of the SL::DB::File object as filename. The filesystem has up to 1000 subdirectories
154 to store the files not to flat in the filesystem. In this Subdirectories for each file an additional subdirectory exists
155 for the versions of this file.
156
157 The Versioning is done via a Versionnumber which is incremented by one for each version.
158 So the Version 2 of the file with the database id 4 is stored as path {root}/0004/4/4_2.
159
160
161 =head1 METHODS
162
163 See methods of L<SL::File::Backend>.
164
165 =head1 SEE ALSO
166
167 L<SL::File::Backend>
168
169 =head1 AUTHOR
170
171 Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
172
173 =cut