Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dated subdirectories #413

Merged
merged 13 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ add_library (rtl_airband_base OBJECT
udp_stream.cpp
logging.cpp
filters.cpp
helper_functions.cpp
${CMAKE_CURRENT_BINARY_DIR}/version.cpp
${rtl_airband_extra_sources}
)
Expand Down Expand Up @@ -366,6 +367,7 @@ if(BUILD_UNITTESTS)
filters.cpp
ctcss.cpp
generate_signal.cpp
helper_functions.cpp
)

add_executable(
Expand Down
16 changes: 10 additions & 6 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ static int parse_outputs(libconfig::Setting &outs, channel_t *channel, int i, in
cerr << "both directory and filename_template required for file\n";
error();
}
fdata->basename = (char *)XCALLOC(1, strlen(outs[o]["directory"]) + strlen(outs[o]["filename_template"]) + 2);
sprintf(fdata->basename, "%s/%s", (const char *)outs[o]["directory"], (const char *)outs[o]["filename_template"]);
fdata->suffix = strdup(".mp3");
fdata->basedir = outs[o]["directory"].c_str();
fdata->basename = outs[o]["filename_template"].c_str();
fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ?
(bool)(outs[o]["dated_subdirectories"]) : false;
fdata->suffix = ".mp3";

fdata->continuous = outs[o].exists("continuous") ?
(bool)(outs[o]["continuous"]) : false;
Expand Down Expand Up @@ -146,9 +148,11 @@ static int parse_outputs(libconfig::Setting &outs, channel_t *channel, int i, in
error();
}

fdata->basename = (char *)XCALLOC(1, strlen(outs[o]["directory"]) + strlen(outs[o]["filename_template"]) + 2);
sprintf(fdata->basename, "%s/%s", (const char *)outs[o]["directory"], (const char *)outs[o]["filename_template"]);
fdata->suffix = strdup(".cf32");
fdata->basedir = outs[o]["directory"].c_str();
fdata->basename = outs[o]["filename_template"].c_str();
fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ?
(bool)(outs[o]["dated_subdirectories"]) : false;
fdata->suffix = ".cf32";

fdata->continuous = outs[o].exists("continuous") ?
(bool)(outs[o]["continuous"]) : false;
Expand Down
88 changes: 88 additions & 0 deletions src/helper_functions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* helper_functions.cpp
*
* Copyright (C) 2023 charlie-foxtrot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <cstddef> // size_t
#include <cstring> // strerror
#include <sys/stat.h> // struct stat, S_ISDIR

#include "logging.h"
#include "helper_functions.h"

using namespace std;

bool dir_exists(const string &dir_path) {
struct stat st;
return (stat(dir_path.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
}

bool file_exists(const string &file_path) {
struct stat st;
return (stat(file_path.c_str(), &st) == 0 && S_ISREG(st.st_mode));
}

bool make_dir(const string &dir_path) {
if (dir_exists(dir_path)) {
return true;
}

if (mkdir(dir_path.c_str(), 0755) != 0) {
log(LOG_ERR, "Could not create directory %s: %s\n", dir_path.c_str(), strerror(errno));
return false;
}
return true;
}

bool make_subdirs(const string &basedir, const string &subdirs) {

// if final directory exists then nothing to do
const string delim = "/";
const string final_path = basedir + delim + subdirs;
if (dir_exists(final_path)) {
return true;
}

// otherwise scan through subdirs for each slash and make each directory. start with index of 0
// to create basedir incase that doesn't exist
size_t index = 0;
while (index != string::npos) {
if (!make_dir(basedir + delim + subdirs.substr(0, index))) {
return false;
}
index = subdirs.find_first_of(delim, index+1);
}

make_dir(final_path);
return dir_exists(final_path);
}

string make_dated_subdirs(const string &basedir, const struct tm *time) {

// use the time to build the date subdirectories
char date_path[11];
strftime(date_path, sizeof(date_path), "%Y/%m/%d", time);
const string date_path_str = string(date_path);

// make all the subdirectories, and return the full path if successful
if (make_subdirs(basedir, date_path_str)) {
return basedir + "/" + date_path_str;
}

// on any error return empty string
return "";
}
32 changes: 32 additions & 0 deletions src/helper_functions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* helper_functions.h
*
* Copyright (C) 2023 charlie-foxtrot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _HELPER_FUNCTIONS_H
#define _HELPER_FUNCTIONS_H

#include <ctime> // struct tm
#include <string>

bool dir_exists(const std::string &dir_path);
bool file_exists(const std::string &file_path);
bool make_dir(const std::string &dir_path);
bool make_subdirs(const std::string &basedir, const std::string &subdirs);
std::string make_dated_subdirs(const std::string &basedir, const struct tm *time);

#endif /* _HELPER_FUNCTIONS_H */
68 changes: 40 additions & 28 deletions src/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <string>
#include <cerrno>
#include <cassert>
#include <sstream>
#include "rtl_airband.h"
#include "input-common.h"
#include "config.h"
#include "helper_functions.h"

void shout_setup(icecast_data *icecast, mix_modes mixmode) {
int ret;
Expand Down Expand Up @@ -235,8 +238,8 @@ int rename_if_exists(char const *oldpath, char const *newpath) {
* as well as the appropriate amount of silence when in continuous mode.
*/
static int open_file(file_data *fdata, mix_modes mixmode, int is_audio) {
int rename_result = rename_if_exists(fdata->file_path, fdata->file_path_tmp);
fdata->f = fopen(fdata->file_path_tmp, fdata->append ? "a+" : "w");
int rename_result = rename_if_exists(fdata->file_path.c_str(), fdata->file_path_tmp.c_str());
fdata->f = fopen(fdata->file_path_tmp.c_str(), fdata->append ? "a+" : "w");
if (fdata->f == NULL) {
return -1;
}
Expand All @@ -245,20 +248,20 @@ static int open_file(file_data *fdata, mix_modes mixmode, int is_audio) {
if (!fdata->append ||
fstat(fileno(fdata->f), &st) != 0 || st.st_size == 0) {
if(!fdata->split_on_transmission) {
log(LOG_INFO, "Writing to %s\n", fdata->file_path);
log(LOG_INFO, "Writing to %s\n", fdata->file_path.c_str());
} else {
debug_print("Writing to %s\n", fdata->file_path_tmp);
debug_print("Writing to %s\n", fdata->file_path_tmp.c_str());
}
return 0;
}
if(rename_result < 0) {
log(LOG_INFO, "Writing to %s\n", fdata->file_path);
debug_print("Writing to %s\n", fdata->file_path_tmp);
log(LOG_INFO, "Writing to %s\n", fdata->file_path.c_str());
debug_print("Writing to %s\n", fdata->file_path_tmp.c_str());
} else {
log(LOG_INFO, "Appending from pos %llu to %s\n",
(unsigned long long)st.st_size, fdata->file_path);
(unsigned long long)st.st_size, fdata->file_path.c_str());
debug_print("Appending from pos %llu to %s\n",
(unsigned long long)st.st_size, fdata->file_path_tmp);
(unsigned long long)st.st_size, fdata->file_path_tmp.c_str());
}

if (is_audio) {
Expand Down Expand Up @@ -303,24 +306,22 @@ static void close_file(channel_t *channel, file_data *fdata) {

if(fdata->type == O_FILE && fdata->f && channel->lame) {
int encoded = lame_encode_flush_nogap(channel->lame, channel->lamebuf, LAMEBUF_SIZE);
debug_print("closing file %s flushed %d\n", fdata->file_path, encoded);
debug_print("closing file %s flushed %d\n", fdata->file_path.c_str(), encoded);

if (encoded > 0) {
size_t written = fwrite((void *)channel->lamebuf, 1, (size_t)encoded, fdata->f);
if (written == 0 || written < (size_t)encoded)
log(LOG_WARNING, "Problem writing %s (%s)\n", fdata->file_path, strerror(errno));
log(LOG_WARNING, "Problem writing %s (%s)\n", fdata->file_path.c_str(), strerror(errno));
}
}

if (fdata->f) {
fclose(fdata->f);
fdata->f = NULL;
rename_if_exists(fdata->file_path_tmp, fdata->file_path);
rename_if_exists(fdata->file_path_tmp.c_str(), fdata->file_path.c_str());
}
free(fdata->file_path);
fdata->file_path = NULL;
free(fdata->file_path_tmp);
fdata->file_path_tmp = NULL;
fdata->file_path.clear();
fdata->file_path_tmp.clear();
}

/*
Expand Down Expand Up @@ -349,7 +350,7 @@ static void close_if_necessary(channel_t *channel, file_data *fdata) {
if (duration_sec > MAX_TRANSMISSION_TIME_SEC ||
(duration_sec > MIN_TRANSMISSION_TIME_SEC && idle_sec > MAX_TRANSMISSION_IDLE_SEC)) {
debug_print("closing file %s, duration %f sec, idle %f sec\n",
fdata->file_path, duration_sec, idle_sec);
fdata->file_path.c_str(), duration_sec, idle_sec);
close_file(channel, fdata);
}
return;
Expand All @@ -368,7 +369,7 @@ static void close_if_necessary(channel_t *channel, file_data *fdata) {
}

if (start_hour != current_hour) {
debug_print("closing file %s after crossing hour boundary\n", fdata->file_path);
debug_print("closing file %s after crossing hour boundary\n", fdata->file_path.c_str());
close_file(channel, fdata);
}
}
Expand Down Expand Up @@ -409,22 +410,33 @@ static bool output_file_ready(channel_t *channel, file_data *fdata, mix_modes mi
return false;
}

size_t file_path_len = strlen(fdata->basename) + strlen(timestamp) + strlen(fdata->suffix) + 11; // include space for '\0' and possible freq in Hz
fdata->file_path = (char *)XCALLOC(1, file_path_len);
if (fdata->include_freq) {
sprintf(fdata->file_path, "%s%s_%d%s", fdata->basename, timestamp, channel->freqlist[channel->freq_idx].frequency, fdata->suffix);
std::string output_dir;
if (fdata->dated_subdirectories) {
output_dir = make_dated_subdirs(fdata->basedir, time);
if (output_dir.empty()) {
log(LOG_ERR, "Failed to create dated subdirectory\n");
return false;
}
} else {
sprintf(fdata->file_path, "%s%s%s", fdata->basename, timestamp, fdata->suffix);
output_dir = fdata->basedir;
make_dir(output_dir);
}

// use a string stream to build the output filepath
std::stringstream ss;
ss << output_dir << '/' << fdata->basename << timestamp;
if (fdata->include_freq) {
ss << '_' << channel->freqlist[channel->freq_idx].frequency;
}
ss << fdata->suffix;
fdata->file_path = ss.str();

static char const *tmp_suffix = ".tmp";
fdata->file_path_tmp = (char *)XCALLOC(1, file_path_len + strlen(tmp_suffix));
sprintf(fdata->file_path_tmp, "%s%s", fdata->file_path, tmp_suffix);
fdata->file_path_tmp = fdata->file_path + ".tmp";

fdata->open_time = fdata->last_write_time = current_time;

if (open_file(fdata, mixmode, is_audio) < 0) {
log(LOG_WARNING, "Cannot open output file %s (%s)\n", fdata->file_path_tmp, strerror(errno));
log(LOG_WARNING, "Cannot open output file %s (%s)\n", fdata->file_path_tmp.c_str(), strerror(errno));
return false;
}

Expand Down Expand Up @@ -511,10 +523,10 @@ void process_outputs(channel_t *channel, int cur_scan_freq) {
if(written < buflen) {
if(ferror(fdata->f))
log(LOG_WARNING, "Cannot write to %s (%s), output disabled\n",
fdata->file_path, strerror(errno));
fdata->file_path.c_str(), strerror(errno));
else
log(LOG_WARNING, "Short write on %s, output disabled\n",
fdata->file_path);
fdata->file_path.c_str());
close_file(channel, fdata);
channel->outputs[k].enabled = false;
}
Expand Down
11 changes: 7 additions & 4 deletions src/rtl_airband.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#ifndef _RTL_AIRBAND_H
#define _RTL_AIRBAND_H 1
#include <string>
#include <cstdio>
#include <complex>
#include <stdint.h> // uint32_t
Expand Down Expand Up @@ -122,10 +123,12 @@ struct icecast_data {
};

struct file_data {
char *basename;
char *suffix;
char *file_path;
char *file_path_tmp;
std::string basedir;
std::string basename;
std::string suffix;
std::string file_path;
std::string file_path_tmp;
bool dated_subdirectories;
bool continuous;
bool append;
bool split_on_transmission;
Expand Down
Loading