Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 744 lines (683 sloc) 27.589 kB
4d4d90b @hzeller o More legalese
authored
1 // Folve - A fuse filesystem that convolves audio files on-the-fly.
2 //
e9c07dd @hzeller o add documentation
authored
3 // Copyright (C) 2012 Henner Zeller <h.zeller@acm.org>
cca212e @hzeller o document compile error with old version of fuse.
authored
4 //
e9c07dd @hzeller o add documentation
authored
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
18 #include "folve-filesystem.h"
19
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
20 #include <FLAC/metadata.h>
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
21 #include <assert.h>
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
22 #include <dirent.h>
e9c07dd @hzeller o add documentation
authored
23 #include <errno.h>
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
24 #include <fcntl.h>
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
25 #include <sndfile.h>
e9c07dd @hzeller o add documentation
authored
26 #include <stdio.h>
27 #include <string.h>
28 #include <strings.h>
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
29 #include <sys/stat.h>
30 #include <sys/types.h>
5830c5a @hzeller o Switch logging to syslog()
authored
31 #include <syslog.h>
e9c07dd @hzeller o add documentation
authored
32 #include <unistd.h>
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
33
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
34 #include <map>
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
35 #include <string>
6c6dedb @hzeller o Some readme tweaks and readability improvements.
authored
36 #include <zita-convolver.h>
e9c07dd @hzeller o add documentation
authored
37
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
38 #include "conversion-buffer.h"
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
39 #include "file-handler-cache.h"
40 #include "file-handler.h"
2fee1d6 @hzeller o extract sound processor
authored
41 #include "sound-processor.h"
8268c37 @hzeller o Bubble up error messages in status server.
authored
42 #include "util.h"
6c6dedb @hzeller o Some readme tweaks and readability improvements.
authored
43
fd65cee @hzeller o add basic zita filter.
authored
44 #include "zita-config.h"
45
8268c37 @hzeller o Bubble up error messages in status server.
authored
46 using folve::Appendf;
3e883b7 @hzeller o Match filters in sequence
authored
47 using folve::StringPrintf;
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
48 using folve::DLogf;
b5c1e96 @hzeller o more useful output on status server.
authored
49
e9c07dd @hzeller o add documentation
authored
50 namespace {
51
5476cc2 @hzeller o provide absolute path.
authored
52 // Very simple filter that just passes the original file through. Used for
53 // everything that is not a sound-file.
f1ef960 @hzeller o a bit HTML polishing.
authored
54 class PassThroughHandler : public FileHandler {
e9c07dd @hzeller o add documentation
authored
55 public:
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
56 PassThroughHandler(int filedes, const std::string &filter_id,
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
57 const HandlerStats &known_stats)
f1ef960 @hzeller o a bit HTML polishing.
authored
58 : FileHandler(filter_id), filedes_(filedes),
59 file_size_(-1), max_accessed_(0), info_stats_(known_stats) {
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
60 DLogf("Creating PassThrough filter for '%s'", known_stats.filename.c_str());
f1ef960 @hzeller o a bit HTML polishing.
authored
61 struct stat st;
62 file_size_ = (Stat(&st) == 0) ? st.st_size : -1;
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
63 info_stats_.filter_dir = ""; // pass through.
e9c07dd @hzeller o add documentation
authored
64 }
f1ef960 @hzeller o a bit HTML polishing.
authored
65 ~PassThroughHandler() { close(filedes_); }
680f590 @hzeller o report opening error.
authored
66
e9c07dd @hzeller o add documentation
authored
67 virtual int Read(char *buf, size_t size, off_t offset) {
68 const int result = pread(filedes_, buf, size, offset);
79e7608 @hzeller o Don't add a message if not necessary to the pass through filter.
authored
69 max_accessed_ = std::max(max_accessed_, (long unsigned int) offset + result);
e9c07dd @hzeller o add documentation
authored
70 return result == -1 ? -errno : result;
71 }
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
72 virtual int Stat(struct stat *st) {
73 return fstat(filedes_, st);
74 }
e2c7d54 @hzeller o There was a report of a 2.7.1 ICE, apparently on a generated move
authored
75 virtual void GetHandlerStatus(HandlerStats *stats) {
8268c37 @hzeller o Bubble up error messages in status server.
authored
76 *stats = info_stats_;
f1ef960 @hzeller o a bit HTML polishing.
authored
77 if (file_size_ > 0) {
78 stats->progress = 1.0 * max_accessed_ / file_size_;
79 }
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
80 }
e2c7d54 @hzeller o There was a report of a 2.7.1 ICE, apparently on a generated move
authored
81
e9c07dd @hzeller o add documentation
authored
82 private:
83 const int filedes_;
f1ef960 @hzeller o a bit HTML polishing.
authored
84 size_t file_size_;
85 long unsigned int max_accessed_;
8268c37 @hzeller o Bubble up error messages in status server.
authored
86 HandlerStats info_stats_;
e9c07dd @hzeller o add documentation
authored
87 };
88
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
89 class SndFileHandler :
90 public FileHandler,
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
91 public ConversionBuffer::SoundSource {
e9c07dd @hzeller o add documentation
authored
92 public:
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
93 // Attempt to create a SndFileHandler from the given file descriptor. This
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
94 // returns NULL if this is not a sound-file or if there is no available
95 // convolution filter configuration available.
8268c37 @hzeller o Bubble up error messages in status server.
authored
96 // "partial_file_info" will be set to information known so far, including
97 // error message.
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
98 static FileHandler *Create(FolveFilesystem *fs,
99 int filedes, const char *fs_path,
100 const std::string &underlying_file,
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
101 const std::string &filter_subdir,
8268c37 @hzeller o Bubble up error messages in status server.
authored
102 const std::string &zita_config_dir,
103 HandlerStats *partial_file_info) {
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
104 SF_INFO in_info;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
105 memset(&in_info, 0, sizeof(in_info));
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
106 SNDFILE *snd = sf_open_fd(filedes, SFM_READ, &in_info, 0);
107 if (snd == NULL) {
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
108 DLogf("File %s: %s", underlying_file.c_str(), sf_strerror(NULL));
8268c37 @hzeller o Bubble up error messages in status server.
authored
109 partial_file_info->message = sf_strerror(NULL);
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
110 return NULL;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
111 }
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
112
7e756eb @hzeller o Fix problem with re-initializing the convproc. Turns out that the c…
authored
113 int bits = 16;
31bd9ce @hzeller o README update.
authored
114 if ((in_info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_24) bits = 24;
115 if ((in_info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_32) bits = 32;
e2619a6 @hzeller o skipping debug message.
authored
116
8268c37 @hzeller o Bubble up error messages in status server.
authored
117 // Remember whatever we could got to know in the partial file info.
118 Appendf(&partial_file_info->format, "%.1fkHz, %d Bit",
119 in_info.samplerate / 1000.0, bits);
120 partial_file_info->duration_seconds = in_info.frames / in_info.samplerate;
121
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
122 SoundProcessor *processor = fs->processor_pool()
123 ->GetOrCreate(zita_config_dir, in_info.samplerate, in_info.channels, bits);
124 if (processor == NULL) {
125 // TODO: depending on why this happened, provide one of these messages.
126 /*
127 partial_file_info_.message = "Problem parsing " + config_path_;
3e883b7 @hzeller o Match filters in sequence
authored
128 partial_file_info->message = "Missing ( " + path_choices[0]
129 + "<br/> ... " + path_choices[max_choice] + " )";
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
130 */
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
131 sf_close(snd);
132 return NULL;
b5c1e96 @hzeller o more useful output on status server.
authored
133 }
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
134 const int seconds = in_info.frames / in_info.samplerate;
135 DLogf("File %s, %.1fkHz, %d Bit, %d:%02d: filter config %s",
136 underlying_file.c_str(), in_info.samplerate / 1000.0, bits,
137 seconds / 60, seconds % 60,
138 processor->config_file().c_str());
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
139 return new SndFileHandler(fs, fs_path, filter_subdir,
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
140 underlying_file, filedes, snd, in_info,
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
141 *partial_file_info, processor);
e9c07dd @hzeller o add documentation
authored
142 }
143
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
144 virtual ~SndFileHandler() {
680f590 @hzeller o report opening error.
authored
145 Close();
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
146 delete output_buffer_;
147 }
148
e9c07dd @hzeller o add documentation
authored
149 virtual int Read(char *buf, size_t size, off_t offset) {
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
150 if (error_) return -1;
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
151 // If this is a skip suspiciously at the very end of the file as
152 // reported by stat, we don't do any encoding, just return garbage.
1118ad9 @hzeller o comment update.
authored
153 // (otherwise we'd to convolve up to that point).
154 //
155 // While indexing, media players do this sometimes apparently.
156 // And sometimes not even to the very end but 'almost' at the end.
157 // So add some FudeOverhang
bcb77e7 @hzeller o Ran with Amarok over my files. Looks like it sometimes only 'almost'
authored
158 static const int kFudgeOverhang = 512;
abb0471 @hzeller o remove debug message from last commit.
authored
159 // But of course only if this is really a skip, not a regular approaching
160 // end-of-file.
bcb77e7 @hzeller o Ran with Amarok over my files. Looks like it sometimes only 'almost'
authored
161 if (output_buffer_->FileSize() < offset
162 && (int) (offset + size + kFudgeOverhang) >= file_stat_.st_size) {
163 const int pretended_bytes = std::min((off_t)size,
164 file_stat_.st_size - offset);
165 if (pretended_bytes > 0) {
166 memset(buf, 0x00, pretended_bytes);
167 return pretended_bytes;
680f590 @hzeller o report opening error.
authored
168 } else {
169 return 0;
170 }
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
171 }
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
172 // The following read might block and call WriteToSoundfile() until the
173 // buffer is filled.
174 return output_buffer_->Read(buf, size, offset);
e9c07dd @hzeller o add documentation
authored
175 }
176
e2c7d54 @hzeller o There was a report of a 2.7.1 ICE, apparently on a generated move
authored
177 virtual void GetHandlerStatus(HandlerStats *stats) {
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
178 folve::MutexLock l(&stats_mutex_);
f00e6a4 @hzeller o Don't do unprotected read on status information such as
authored
179 if (processor_ != NULL) {
180 base_stats_.max_output_value = processor_->max_output_value();
181 }
8268c37 @hzeller o Bubble up error messages in status server.
authored
182 *stats = base_stats_;
2fee1d6 @hzeller o extract sound processor
authored
183 const int frames_done = in_info_.frames - input_frames_left_;
184 if (frames_done == 0 || in_info_.frames == 0)
223f392 @hzeller o Have a separate HandlerStats object that contains relevant
authored
185 stats->progress = 0.0;
186 else
2fee1d6 @hzeller o extract sound processor
authored
187 stats->progress = 1.0 * frames_done / in_info_.frames;
f00e6a4 @hzeller o Don't do unprotected read on status information such as
authored
188
d93a35b @hzeller o Uh, typo in gapless message.
authored
189 if (base_stats_.max_output_value > 1.0) {
a5c6382 @hzeller o Remember the max output value in the stats
authored
190 // TODO: the status server could inspect this value and make better
191 // rendering.
60477fe @hzeller o Detect output overdrive and give useful message what to do.
authored
192 base_stats_.message =
ce22b6e @hzeller o change wording from 'overdrive' to 'clipping' so that
authored
193 StringPrintf("Output clipping! "
60477fe @hzeller o Detect output overdrive and give useful message what to do.
authored
194 "(max=%.3f; Multiply gain with <= %.5f<br/>in %s)",
a5c6382 @hzeller o Remember the max output value in the stats
authored
195 base_stats_.max_output_value,
196 1.0 / base_stats_.max_output_value,
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
197 processor_ != NULL
198 ? processor_->config_file().c_str()
199 : "filter");
60477fe @hzeller o Detect output overdrive and give useful message what to do.
authored
200 }
b5c1e96 @hzeller o more useful output on status server.
authored
201 }
202
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
203 virtual int Stat(struct stat *st) {
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
204 if (output_buffer_->FileSize() > start_estimating_size_) {
2fee1d6 @hzeller o extract sound processor
authored
205 const int frames_done = in_info_.frames - input_frames_left_;
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
206 if (frames_done > 0) {
2fee1d6 @hzeller o extract sound processor
authored
207 const float estimated_end = 1.0 * in_info_.frames / frames_done;
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
208 off_t new_size = estimated_end * output_buffer_->FileSize();
209 // Report a bit bigger size which is less harmful than programs
210 // reading short.
211 new_size += 16384;
453489f @hzeller o smallish whitespace changes.
authored
212 if (new_size > file_stat_.st_size) { // Only go forward in size.
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
213 file_stat_.st_size = new_size;
214 }
215 }
216 }
217 *st = file_stat_;
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
218 return 0;
219 }
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
220
e9c07dd @hzeller o add documentation
authored
221 private:
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
222 // TODO(hzeller): trim parameter list.
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
223 SndFileHandler(FolveFilesystem *fs, const char *fs_path,
224 const std::string &filter_dir,
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
225 const std::string &underlying_file,
226 int filedes, SNDFILE *snd_in,
8268c37 @hzeller o Bubble up error messages in status server.
authored
227 const SF_INFO &in_info, const HandlerStats &file_info,
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
228 SoundProcessor *processor)
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
229 : FileHandler(filter_dir), fs_(fs),
2fee1d6 @hzeller o extract sound processor
authored
230 filedes_(filedes), snd_in_(snd_in), in_info_(in_info),
231 base_stats_(file_info),
b5c1e96 @hzeller o more useful output on status server.
authored
232 error_(false), output_buffer_(NULL),
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
233 snd_out_(NULL), processor_(processor),
60477fe @hzeller o Detect output overdrive and give useful message what to do.
authored
234 input_frames_left_(in_info.frames) {
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
235
236 // Initial stat that we're going to report to clients. We'll adapt
237 // the filesize as we see it grow. Some clients continuously monitor
238 // the size of the file to check when to stop.
239 fstat(filedes_, &file_stat_);
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
240 start_estimating_size_ = 0.4 * file_stat_.st_size;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
241
8e36eff @hzeller o comment update.
authored
242 // The flac header we get is more rich than what we can create via
243 // sndfile. So if we have one, just copy it.
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
244 copy_flac_header_verbatim_ = LooksLikeInputIsFlac(in_info, filedes);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
245
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
246 // Create a conversion buffer that creates a soundfile of a particular
8e36eff @hzeller o comment update.
authored
247 // format that we choose here. Essentially we want to generate mostly what
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
248 // our input is.
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
249 SF_INFO out_info = in_info;
31bd9ce @hzeller o README update.
authored
250 out_info.seekable = 0;
251 if ((in_info.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_OGG) {
8e36eff @hzeller o comment update.
authored
252 // If the input was ogg, we're re-coding this to flac, because it
253 // wouldn't let us stream the output.
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
254 out_info.format = SF_FORMAT_FLAC;
f11d8cd @hzeller o implement initial skip mode detection.
authored
255 out_info.format |= SF_FORMAT_PCM_16;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
256 }
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
257 else if ((in_info.format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV) {
258 out_info.format = SF_FORMAT_FLAC; // recode as flac.
259 out_info.format |= SF_FORMAT_PCM_24;
31bd9ce @hzeller o README update.
authored
260 }
261 else { // original format.
262 out_info.format = in_info.format;
263 }
264
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
265 output_buffer_ = new ConversionBuffer(this, out_info);
266 }
267
f8cb9e5 @hzeller o redacting MD5.
authored
268 virtual void SetOutputSoundfile(ConversionBuffer *out_buffer,
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
269 SNDFILE *sndfile) {
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
270 snd_out_ = sndfile;
271 if (snd_out_ == NULL) {
272 error_ = true;
5830c5a @hzeller o Switch logging to syslog()
authored
273 syslog(LOG_ERR, "Opening output: %s", sf_strerror(NULL));
8268c37 @hzeller o Bubble up error messages in status server.
authored
274 base_stats_.message = sf_strerror(NULL);
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
275 return;
276 }
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
277 if (copy_flac_header_verbatim_) {
8e36eff @hzeller o comment update.
authored
278 out_buffer->set_sndfile_writes_enabled(false);
f8cb9e5 @hzeller o redacting MD5.
authored
279 CopyFlacHeader(out_buffer);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
280 } else {
f8cb9e5 @hzeller o redacting MD5.
authored
281 out_buffer->set_sndfile_writes_enabled(true);
8e36eff @hzeller o comment update.
authored
282 GenerateHeaderFromInputFile(out_buffer);
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
283 }
284 // Now flush the header: that way if someone only reads the metadata, then
285 // our AddMoreSoundData() is never called.
8e36eff @hzeller o comment update.
authored
286 // We need to do this even if we copied our own header: that way we make
287 // sure that the sndfile-header is flushed into the nirwana before we
288 // re-enable sndfile_writes.
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
289 sf_command(snd_out_, SFC_UPDATE_HEADER_NOW, NULL, 0);
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
290
291 // -- time for some hackery ...
292 // If we have copied the header over from the original, we need to
293 // redact the values for min/max blocksize and min/max framesize with
294 // what SNDFILE is going to use, otherwise programs will trip over this.
295 // http://flac.sourceforge.net/format.html
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
296 if (copy_flac_header_verbatim_) {
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
297 out_buffer->WriteCharAt((1152 & 0xFF00) >> 8, 8);
298 out_buffer->WriteCharAt((1152 & 0x00FF) , 9);
335d185 @hzeller o Fix blocksize header; this was once an experiment that seems
authored
299 out_buffer->WriteCharAt((1152 & 0xFF00) >> 8, 10);
300 out_buffer->WriteCharAt((1152 & 0x00FF) , 11);
301 for (int i = 12; i < 18; ++i) out_buffer->WriteCharAt(0, i); // framesize
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
302 } else {
303 // .. and if SNDFILE writes the header, it misses out in writing the
304 // number of samples to be expected. So let's fill that in.
305 // The MD5 sum starts at position strlen("fLaC") + 4 + 18 = 26
306 // The 32 bits before that are the samples (and another 4 bit before that,
307 // ignoring that for now).
2fee1d6 @hzeller o extract sound processor
authored
308 out_buffer->WriteCharAt((in_info_.frames & 0xFF000000) >> 24, 22);
309 out_buffer->WriteCharAt((in_info_.frames & 0x00FF0000) >> 16, 23);
310 out_buffer->WriteCharAt((in_info_.frames & 0x0000FF00) >> 8, 24);
311 out_buffer->WriteCharAt((in_info_.frames & 0x000000FF), 25);
a85bebb @hzeller o Fix Flac header manually. Luckily the streamheader is a fixed
authored
312 }
f8cb9e5 @hzeller o redacting MD5.
authored
313
314 out_buffer->set_sndfile_writes_enabled(true); // ready for sound-stream.
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
315 DLogf("Header init done (%s).", base_stats_.filename.c_str());
550c4e0 @hzeller o allow short read in header area, but not in the stream
authored
316 out_buffer->HeaderFinished();
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
317 }
318
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
319 bool HasStarted() { return in_info_.frames != input_frames_left_; }
320 virtual bool AcceptProcessor(SoundProcessor *passover_processor) {
321 if (HasStarted()) {
322 DLogf("Gapless attempt: Cannot bridge gap to already open file %s",
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
323 base_stats_.filename.c_str());
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
324 return false;
1113409 @hzeller o Diagnostic if gapless cannot be done, because the other file is
authored
325 }
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
326 assert(processor_);
327 if (passover_processor->config_file() != processor_->config_file()
328 || (passover_processor->config_file_timestamp()
329 != processor_->config_file_timestamp())) {
330 DLogf("Gapless: Configuration changed; can't use %p to join gapless.",
331 passover_processor);
9af16fe @hzeller o Gapless string should break when configuration changes.
authored
332 return false;
333 }
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
334 // Ok, so don't use the processor we already have, but use the other one.
335 fs_->processor_pool()->Return(processor_);
336 processor_ = passover_processor;
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
337 if (!processor_->is_input_buffer_complete()) {
7b499ea @hzeller o Uh, log() is actually ln() and log10f() is log() :)
authored
338 // Fill with our beginning so that the donor can finish its processing.
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
339 input_frames_left_ -= processor_->FillBuffer(snd_in_);
340 }
341 base_stats_.in_gapless = true;
342 return true;
343 }
344
ac40d85 @hzeller o Make sure we match the alphabetically next file with
authored
345 static bool ExtractDirAndSuffix(const std::string &filename,
346 std::string *dir, std::string *suffix) {
347 const std::string::size_type slash_pos = filename.find_last_of('/');
348 if (slash_pos == std::string::npos) return false;
349 *dir = filename.substr(0, slash_pos + 1);
350 const std::string::size_type dot_pos = filename.find_last_of('.');
351 if (dot_pos != std::string::npos && dot_pos > slash_pos) {
352 *suffix = filename.substr(dot_pos);
353 }
354 return true;
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
355 }
356
47c2b03 @hzeller o better skip mode detection. Only if someone _really_ jumps to the
authored
357 virtual bool AddMoreSoundData() {
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
358 if (!input_frames_left_)
359 return false;
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
360 if (processor_->pending_writes() > 0) {
361 processor_->WriteProcessed(snd_out_, processor_->pending_writes());
362 return input_frames_left_;
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
363 }
2fee1d6 @hzeller o extract sound processor
authored
364 const int r = processor_->FillBuffer(snd_in_);
9c0ba15 @hzeller o work around broken files - don't endlessloop :)
authored
365 if (r == 0) {
2fee1d6 @hzeller o extract sound processor
authored
366 syslog(LOG_ERR, "Expected %d frames left, "
5830c5a @hzeller o Switch logging to syslog()
authored
367 "but got EOF; corrupt file '%s' ?",
2fee1d6 @hzeller o extract sound processor
authored
368 input_frames_left_, base_stats_.filename.c_str());
0706452 @hzeller o Report broken input file in UI.
authored
369 base_stats_.message = "Premature EOF in input file.";
9c0ba15 @hzeller o work around broken files - don't endlessloop :)
authored
370 input_frames_left_ = 0;
371 Close();
372 return false;
373 }
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
374 stats_mutex_.Lock();
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
375 input_frames_left_ -= r;
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
376 stats_mutex_.Unlock();
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
377 if (!input_frames_left_ && !processor_->is_input_buffer_complete()
378 && fs_->gapless_processing()) {
379 typedef std::set<std::string> DirSet;
ac40d85 @hzeller o Make sure we match the alphabetically next file with
authored
380 DirSet dirset;
381 std::string fs_dir, file_suffix;
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
382 FileHandler *next_file = NULL;
ac40d85 @hzeller o Make sure we match the alphabetically next file with
authored
383 DirSet::const_iterator found;
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
384 const bool passed_processor
ac40d85 @hzeller o Make sure we match the alphabetically next file with
authored
385 = (ExtractDirAndSuffix(base_stats_.filename, &fs_dir, &file_suffix)
386 && fs_->ListDirectory(fs_dir, file_suffix, &dirset)
387 && (found = dirset.upper_bound(base_stats_.filename)) != dirset.end()
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
388 && (next_file = fs_->GetOrCreateHandler(found->c_str()))
389 && next_file->AcceptProcessor(processor_));
390 if (passed_processor) {
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
391 DLogf("Processor %p: Gapless pass-on from "
392 "'%s' to alphabetically next '%s'", processor_,
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
393 base_stats_.filename.c_str(), found->c_str());
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
394 }
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
395 stats_mutex_.Lock();
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
396 processor_->WriteProcessed(snd_out_, r);
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
397 stats_mutex_.Unlock();
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
398 if (passed_processor) {
399 base_stats_.out_gapless = true;
7b499ea @hzeller o Uh, log() is actually ln() and log10f() is log() :)
authored
400 SaveOutputValues();
ac40d85 @hzeller o Make sure we match the alphabetically next file with
authored
401 processor_ = NULL; // we handed over ownership.
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
402 }
403 if (next_file) fs_->Close(found->c_str(), next_file);
404 } else {
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
405 stats_mutex_.Lock();
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
406 processor_->WriteProcessed(snd_out_, r);
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
407 stats_mutex_.Unlock();
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
408 }
31bd9ce @hzeller o README update.
authored
409 if (input_frames_left_ == 0) {
680f590 @hzeller o report opening error.
authored
410 Close();
31bd9ce @hzeller o README update.
authored
411 }
0edb358 @hzeller o attempt to remove global references in the zita config.
authored
412 return input_frames_left_;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
413 }
fd4ed76 @hzeller o write to a file instead of a buffer.
authored
414
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
415 // TODO add as a utility function to ConversionBuffer ?
8e36eff @hzeller o comment update.
authored
416 void CopyBytes(int fd, off_t pos, ConversionBuffer *out, size_t len) {
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
417 char buf[256];
418 while (len > 0) {
419 ssize_t r = pread(fd, buf, std::min(sizeof(buf), len), pos);
8e36eff @hzeller o comment update.
authored
420 if (r <= 0) return;
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
421 out->Append(buf, r);
422 len -= r;
423 pos += r;
424 }
425 }
426
f8cb9e5 @hzeller o redacting MD5.
authored
427 void CopyFlacHeader(ConversionBuffer *out_buffer) {
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
428 DLogf("Provide FLAC header from original file %s",
429 base_stats_.filename.c_str());
f8cb9e5 @hzeller o redacting MD5.
authored
430 out_buffer->Append("fLaC", 4);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
431 off_t pos = 4;
432 unsigned char header[4];
f8cb9e5 @hzeller o redacting MD5.
authored
433 bool need_finish_padding = false;
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
434 while (pread(filedes_, header, sizeof(header), pos) == sizeof(header)) {
435 pos += sizeof(header);
436 bool is_last = header[0] & 0x80;
437 unsigned int type = header[0] & 0x7F;
438 unsigned int byte_len = (header[1] << 16) + (header[2] << 8) + header[3];
5098e07 @hzeller o improve logging.
authored
439 const char *extra_info = "";
f8cb9e5 @hzeller o redacting MD5.
authored
440 need_finish_padding = false;
441 if (type == FLAC__METADATA_TYPE_STREAMINFO && byte_len == 34) {
442 out_buffer->Append(&header, sizeof(header));
8e36eff @hzeller o comment update.
authored
443 // Copy everything but the MD5 at the end - which we set to empty.
f8cb9e5 @hzeller o redacting MD5.
authored
444 CopyBytes(filedes_, pos, out_buffer, byte_len - 16);
445 for (int i = 0; i < 16; ++i) out_buffer->Append("\0", 1);
5098e07 @hzeller o improve logging.
authored
446 extra_info = "Streaminfo; redact MD5.";
f8cb9e5 @hzeller o redacting MD5.
authored
447 }
448 else if (type == FLAC__METADATA_TYPE_SEEKTABLE) {
449 // The SEEKTABLE header we skip, because it is bogus after encoding.
5830c5a @hzeller o Switch logging to syslog()
authored
450 // TODO append log (skip the seektable)
f8cb9e5 @hzeller o redacting MD5.
authored
451 need_finish_padding = is_last; // if we were last, force finish block.
5098e07 @hzeller o improve logging.
authored
452 extra_info = "Skip seektable.";
f8cb9e5 @hzeller o redacting MD5.
authored
453 }
454 else {
455 out_buffer->Append(&header, sizeof(header));
456 CopyBytes(filedes_, pos, out_buffer, byte_len);
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
457 }
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
458 DLogf(" %02x %02x %02x %02x type: %d, len: %6u %s %s ",
459 header[0], header[1], header[2], header[3],
460 type, byte_len, is_last ? "(last)" : "(cont)", extra_info);
f8cb9e5 @hzeller o redacting MD5.
authored
461 pos += byte_len;
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
462 if (is_last)
463 break;
464 }
f8cb9e5 @hzeller o redacting MD5.
authored
465 if (need_finish_padding) { // if the last block was not is_last: pad.
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
466 DLogf("write padding");
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
467 memset(&header, 0, sizeof(header));
468 header[0] = 0x80 /* is last */ | FLAC__METADATA_TYPE_PADDING;
f8cb9e5 @hzeller o redacting MD5.
authored
469 out_buffer->Append(&header, sizeof(header));
7e27dab @hzeller o initial version of copying header. Not sure yet though if
authored
470 }
471 }
472
8e36eff @hzeller o comment update.
authored
473 void GenerateHeaderFromInputFile(ConversionBuffer *out_buffer) {
7689c9a @hzeller o preparation to extract classes from folve-filesystem into their
authored
474 DLogf("Generate header from original ID3-tags.");
8e36eff @hzeller o comment update.
authored
475 out_buffer->set_sndfile_writes_enabled(true);
476 // Copy ID tags that are supported by sndfile.
477 for (int i = SF_STR_FIRST; i <= SF_STR_LAST; ++i) {
478 const char *s = sf_get_string(snd_in_, i);
479 if (s != NULL) {
480 sf_set_string(snd_out_, i, s);
481 }
482 }
483 }
484
7b499ea @hzeller o Uh, log() is actually ln() and log10f() is log() :)
authored
485 void SaveOutputValues() {
a5c6382 @hzeller o Remember the max output value in the stats
authored
486 if (processor_) {
487 base_stats_.max_output_value = processor_->max_output_value();
fb3fb9a @hzeller o Log clipping even if we pass on the processor.
authored
488 processor_->ResetMaxValues();
60477fe @hzeller o Detect output overdrive and give useful message what to do.
authored
489 }
fb3fb9a @hzeller o Log clipping even if we pass on the processor.
authored
490 }
a5c6382 @hzeller o Remember the max output value in the stats
authored
491
fb3fb9a @hzeller o Log clipping even if we pass on the processor.
authored
492 void Close() {
493 if (snd_out_ == NULL) return; // done.
7b499ea @hzeller o Uh, log() is actually ln() and log10f() is log() :)
authored
494 SaveOutputValues();
a5c6382 @hzeller o Remember the max output value in the stats
authored
495 if (base_stats_.max_output_value > 1.0) {
496 syslog(LOG_ERR, "Observed output clipping in '%s': "
497 "Max=%.3f; Multiply gain with <= %.5f in %s",
498 base_stats_.filename.c_str(), base_stats_.max_output_value,
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
499 1.0 / base_stats_.max_output_value,
500 processor_ != NULL ? processor_->config_file().c_str() : "filter");
a5c6382 @hzeller o Remember the max output value in the stats
authored
501 }
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
502 fs_->processor_pool()->Return(processor_);
7b499ea @hzeller o Uh, log() is actually ln() and log10f() is log() :)
authored
503 processor_ = NULL;
0b0bd33 @hzeller Fix bug: the last couple of sound-samples were not flushed. We want
authored
504 // We can't disable buffer writes here, because outfile closing will flush
505 // the last couple of sound samples.
680f590 @hzeller o report opening error.
authored
506 if (snd_in_) sf_close(snd_in_);
507 if (snd_out_) sf_close(snd_out_);
508 snd_out_ = NULL;
509 close(filedes_);
510 }
511
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
512 bool LooksLikeInputIsFlac(const SF_INFO &sndinfo, int filedes) {
513 if ((sndinfo.format & SF_FORMAT_TYPEMASK) != SF_FORMAT_FLAC)
514 return false;
515 // However some files contain flac encoded stuff, but are not flac files
516 // by themselve. So we can't copy headers verbatim. Sanity check header.
517 char flac_magic[4];
518 if (pread(filedes, flac_magic, sizeof(flac_magic), 0) != sizeof(flac_magic))
519 return false;
520 return memcmp(flac_magic, "fLaC", sizeof(flac_magic)) == 0;
521 }
522
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
523 FolveFilesystem *const fs_;
e9c07dd @hzeller o add documentation
authored
524 const int filedes_;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
525 SNDFILE *const snd_in_;
2fee1d6 @hzeller o extract sound processor
authored
526 const SF_INFO in_info_;
7c7bda6 @hzeller o attempt to create a sndfile for every file, if that fails, fall
authored
527
b4eec5c @hzeller o libboost caused too many troubles in embedded systems with weak
authored
528 folve::Mutex stats_mutex_;
2fee1d6 @hzeller o extract sound processor
authored
529 HandlerStats base_stats_; // UI information about current file.
530
531 struct stat file_stat_; // we dynamically report a changing size.
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
532 off_t start_estimating_size_; // essentially const.
533
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
534 bool error_;
bea11d6 @hzeller o Don't use snprintf, but use a new function Appendf() to assemble
authored
535 bool copy_flac_header_verbatim_;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
536 ConversionBuffer *output_buffer_;
537 SNDFILE *snd_out_;
538
539 // Used in conversion.
2fee1d6 @hzeller o extract sound processor
authored
540 SoundProcessor *processor_;
6e895e3 @hzeller o add conversion buffer that uses the virtual file provided
authored
541 int input_frames_left_;
e9c07dd @hzeller o add documentation
authored
542 };
543 } // namespace
544
8c7e5c1 @hzeller o Only if -D is given, the debug toggle is active in the UI.
authored
545 FolveFilesystem::FolveFilesystem()
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
546 : debug_ui_enabled_(false), gapless_processing_(false),
950f192 @hzeller o Wire up processor-pool to folve-filesystem.
authored
547 open_file_cache_(4), processor_pool_(3),
548 total_file_openings_(0), total_file_reopen_(0) {
8c7e5c1 @hzeller o Only if -D is given, the debug toggle is active in the UI.
authored
549 }
ba7fde8 @hzeller o implement size estimate. If the user stat() or fstat() the file,
authored
550
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
551 FileHandler *FolveFilesystem::CreateFromDescriptor(
552 int filedes,
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
553 const std::string &config_dir,
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
554 const char *fs_path,
555 const std::string &underlying_file) {
8268c37 @hzeller o Bubble up error messages in status server.
authored
556 HandlerStats file_info;
557 file_info.filename = fs_path;
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
558 file_info.filter_dir = config_dir;
559 if (!config_dir.empty()) {
560 const std::string full_config_path = base_config_dir_ + "/" + config_dir;
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
561 FileHandler *filter = SndFileHandler::Create(this, filedes, fs_path,
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
562 underlying_file,
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
563 config_dir, full_config_path,
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
564 &file_info);
565 if (filter != NULL) return filter;
566 }
8b18938 @hzeller o some comment changes. Ready to do some flac handling.
authored
567 // Every other file-type is just passed through as is.
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
568 return new PassThroughHandler(filedes, config_dir, file_info);
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
569 }
570
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
571 std::string FolveFilesystem::CacheKey(const std::string &config_path,
572 const char *fs_path) {
573 return config_path + fs_path;
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
574 }
575
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
576 FileHandler *FolveFilesystem::GetOrCreateHandler(const char *fs_path) {
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
577 const std::string config_path = current_config_subdir_;
578 const std::string cache_key = CacheKey(config_path, fs_path);
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
579 const std::string underlying_file = underlying_dir() + fs_path;
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
580 FileHandler *handler = open_file_cache_.FindAndPin(cache_key);
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
581 if (handler == NULL) {
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
582 int filedes = open(underlying_file.c_str(), O_RDONLY);
680f590 @hzeller o report opening error.
authored
583 if (filedes < 0)
584 return NULL;
b5c1e96 @hzeller o more useful output on status server.
authored
585 ++total_file_openings_;
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
586 handler = CreateFromDescriptor(filedes, config_path,
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
587 fs_path, underlying_file);
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
588 handler = open_file_cache_.InsertPinned(cache_key, handler);
b5c1e96 @hzeller o more useful output on status server.
authored
589 } else {
590 ++total_file_reopen_;
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
591 }
592 return handler;
e9c07dd @hzeller o add documentation
authored
593 }
594
a9d3e53 @hzeller o Found a project name: "Folve". Some renamings because of that.
authored
595 int FolveFilesystem::StatByFilename(const char *fs_path, struct stat *st) {
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
596 const std::string cache_key = CacheKey(current_config_subdir_, fs_path);
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
597 FileHandler *handler = open_file_cache_.FindAndPin(cache_key);
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
598 if (handler == 0)
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
599 return -1;
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
600 ssize_t result = handler->Stat(st);
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
601 open_file_cache_.Unpin(cache_key);
15b305a @hzeller o Build infrastructure to re-use file handler objects. Files seem to
authored
602 return result;
9a8ea56 @hzeller o add infrastructure to provide Stat() output of currently open
authored
603 }
604
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
605 void FolveFilesystem::Close(const char *fs_path, const FileHandler *handler) {
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
606 assert(handler != NULL);
607 const std::string cache_key = CacheKey(handler->filter_dir(), fs_path);
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
608 open_file_cache_.Unpin(cache_key);
609 }
610
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
611 static bool IsDirectory(const std::string &path) {
612 if (path.empty()) return false;
613 struct stat st;
614 if (stat(path.c_str(), &st) != 0)
615 return false;
616 return (st.st_mode & S_IFMT) == S_IFDIR;
617 }
618
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
619 bool FolveFilesystem::ListDirectory(const std::string &fs_dir,
ac40d85 @hzeller o Make sure we match the alphabetically next file with
authored
620 const std::string &suffix,
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
621 std::set<std::string> *files) {
622 const std::string real_dir = underlying_dir() + fs_dir;
623 DIR *dp = opendir(real_dir.c_str());
624 if (dp == NULL) return false;
625 struct dirent *dent;
626 while ((dent = readdir(dp)) != NULL) {
ac40d85 @hzeller o Make sure we match the alphabetically next file with
authored
627 if (!folve::HasSuffix(dent->d_name, suffix))
628 continue;
08bbf28 @hzeller o First shot at gapless. Works pretty well for my test-case.
authored
629 files->insert(fs_dir + dent->d_name);
630 }
631 closedir(dp);
632 return true;
633 }
634
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
635 bool FolveFilesystem::SanitizeConfigSubdir(std::string *subdir_path) const {
636 if (base_config_dir_.length() + 1 + subdir_path->length() > PATH_MAX)
637 return false; // uh, someone wants to buffer overflow us ?
638 const std::string to_verify_path = base_config_dir_ + "/" + *subdir_path;
639 char all_path[PATH_MAX];
640 // This will as well eat symbolic links that break out, though one could
641 // argue that that would be sane. We could think of some light
642 // canonicalization that only removes ./ and ../
643 const char *verified = realpath(to_verify_path.c_str(), all_path);
644 if (verified == NULL) { // bogus directory.
645 return false;
646 }
647 if (strncmp(verified, base_config_dir_.c_str(),
648 base_config_dir_.length()) != 0) {
649 // Attempt to break out with ../-tricks.
650 return false;
651 }
652 if (!IsDirectory(verified))
653 return false;
654
655 // Derive from sanitized dir. So someone can write lowpass/../highpass
656 // or '.' for empty filter. Or ./highpass. And all work.
657 *subdir_path = ((strlen(verified) == base_config_dir_.length())
658 ? "" // chose subdir '.'
659 : verified + base_config_dir_.length() + 1 /*slash*/);
660 return true;
661 }
662
c113afd @hzeller o Adapt curl, wget documentation
authored
663 bool FolveFilesystem::SwitchCurrentConfigDir(const std::string &subdir_in) {
664 std::string subdir = subdir_in;
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
665 if (!subdir.empty() && !SanitizeConfigSubdir(&subdir)) {
666 syslog(LOG_INFO, "Invalid config switch attempt to '%s'",
667 subdir_in.c_str());
668 return false;
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
669 }
670 if (subdir != current_config_subdir_) {
671 current_config_subdir_ = subdir;
672 if (subdir.empty()) {
5830c5a @hzeller o Switch logging to syslog()
authored
673 syslog(LOG_INFO, "Switching to pass-through mode.");
674 } else {
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
675 syslog(LOG_INFO, "Switching config directory to '%s'", subdir.c_str());
5830c5a @hzeller o Switch logging to syslog()
authored
676 }
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
677 return true;
5830c5a @hzeller o Switch logging to syslog()
authored
678 }
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
679 return false;
e9c07dd @hzeller o add documentation
authored
680 }
fd65cee @hzeller o add basic zita filter.
authored
681
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
682 bool FolveFilesystem::CheckInitialized() {
683 if (underlying_dir().empty()) {
684 fprintf(stderr, "Don't know the underlying directory to read from.\n");
685 return false;
686 }
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
687
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
688 if (!IsDirectory(underlying_dir())) {
689 fprintf(stderr, "<underlying-dir>: '%s' not a directory.\n",
690 underlying_dir().c_str());
691 return false;
692 }
693
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
694 if (base_config_dir_.empty() || !IsDirectory(base_config_dir_)) {
695 fprintf(stderr, "<config-dir>: '%s' not a directory.\n",
696 base_config_dir_.c_str());
697 return false;
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
698 }
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
699
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
700 return true;
701 }
702
703 void FolveFilesystem::SetupInitialConfig() {
704 std::set<std::string> available_dirs = ListConfigDirs(true);
705 // Some sanity checks.
706 if (available_dirs.size() == 1) {
707 syslog(LOG_NOTICE, "No filter configuration directories given. "
708 "Any files will be just passed through verbatim.");
709 }
710 if (available_dirs.size() > 1) {
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
711 // By default, lets set the index to the first filter the user provided.
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
712 SwitchCurrentConfigDir(*++available_dirs.begin());
ee675fe @hzeller o Provide a way to select between different filter diretories.
authored
713 }
fd65cee @hzeller o add basic zita filter.
authored
714 }
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
715
716 const std::set<std::string> FolveFilesystem::GetAvailableConfigDirs() const {
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
717 return ListConfigDirs(false);
718 }
719
720 const std::set<std::string> FolveFilesystem::ListConfigDirs(bool warn_invalid)
721 const {
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
722 std::set<std::string> result;
723 result.insert(""); // empty directory: pass-through.
724 DIR *dp = opendir(base_config_dir_.c_str());
725 if (dp == NULL) return result;
726 struct dirent *dent;
727 while ((dent = readdir(dp)) != NULL) {
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
728 std::string subdir = dent->d_name;
729 if (subdir == "." || subdir == "..")
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
730 continue;
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
731 if (!SanitizeConfigSubdir(&subdir)) {
732 if (warn_invalid) {
733 syslog(LOG_INFO, "Note: '%s' ignored in config directory; not a "
734 "directory or pointing outside base directory.", dent->d_name);
735 }
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
736 continue;
a90d922 @hzeller o Separate sanitizing of config subdirectories.
authored
737 }
738 result.insert(subdir);
31096bb @hzeller o remove option -c and add -C: give a base directory for all filters.
authored
739 }
740 closedir(dp);
741 return result;
742 }
743
Something went wrong with that request. Please try again.