From 239fab39974768b4d0ec22cf869e3f336ec48b46 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 6 Apr 2021 20:38:34 -0700 Subject: [PATCH] initial design complete created seederd created init scripts which interface with systemd created rss helper programs to generate feeds --- .gitignore | 30 + Dockerfile | 3 - Makefile | 22 - Makefile.am | 1 + README.txt | 24 + configure.ac | 83 +++ entrypoint.sh | 14 + init/Makefile.am | 17 + init/remove-nginx.sh | 8 + init/remove-systemd.sh | 12 + init/remove.sh | 5 + init/setup-nginx.sh | 36 ++ init/setup-systemd.sh | 46 ++ init/setup.sh | 24 + m4/m4_ax_cxx_compile_stdcxx.m4 | 962 +++++++++++++++++++++++++++++++++ rss/Makefile.am | 126 +++++ rss/inc/add.h | 25 + rss/inc/default.h | 14 + rss/inc/escape.h | 11 + rss/inc/info.h | 13 + rss/inc/log.h | 15 + rss/inc/main.h | 15 + rss/inc/meta.h | 15 + rss/inc/next.h | 10 + rss/inc/opt.h | 35 ++ rss/inc/rss.h | 29 + rss/inc/util.h | 9 + rss/src/add.c | 65 +++ rss/src/default.c | 16 + rss/src/escape.c | 84 +++ rss/src/info.c | 36 ++ rss/src/log.c | 16 + rss/src/main.c | 69 +++ rss/src/meta.c | 100 ++++ rss/src/next.c | 18 + rss/src/opt/desc.c | 19 + rss/src/opt/feed.c | 26 + rss/src/opt/link.c | 19 + rss/src/opt/meta.c | 74 +++ rss/src/opt/name.c | 19 + rss/src/opt/out.c | 16 + rss/src/util/now.c | 15 + rss/test/add.tests.c | 52 ++ rss/test/add.tests.h | 15 + rss/test/escape.tests.c | 55 ++ rss/test/escape.tests.h | 11 + rss/test/info.tests.c | 41 ++ rss/test/info.tests.h | 11 + rss/test/meta.tests.c | 219 ++++++++ rss/test/meta.tests.h | 14 + rss/test/next.tests.c | 32 ++ rss/test/next.tests.h | 15 + rss/test/opt.desc.tests.c | 19 + rss/test/opt.desc.tests.h | 11 + rss/test/opt.feed.tests.c | 25 + rss/test/opt.feed.tests.h | 11 + rss/test/opt.link.tests.c | 19 + rss/test/opt.link.tests.h | 11 + rss/test/opt.meta.tests.c | 21 + rss/test/opt.meta.tests.h | 11 + rss/test/opt.name.tests.c | 19 + rss/test/opt.name.tests.h | 11 + rss/test/opt.out.tests.c | 16 + rss/test/opt.out.tests.h | 11 + rss/test/test_utils.c | 46 ++ rss/test/test_utils.h | 54 ++ rss/test/util.now.tests.c | 20 + rss/test/util.now.tests.h | 11 + seederd/Makefile.am | 21 + seederd/inc/consts.h | 7 + seederd/inc/feed.hpp | 22 + seederd/inc/main.h | 23 + seederd/inc/seed.hpp | 25 + seederd/inc/torrent.hpp | 37 ++ seederd/src/feed.cpp | 7 + seederd/src/main.cpp | 124 +++++ seederd/src/seed.cpp | 5 + seederd/src/torrent.cpp | 92 ++++ 78 files changed, 3315 insertions(+), 25 deletions(-) create mode 100644 .gitignore delete mode 100644 Dockerfile delete mode 100644 Makefile create mode 100644 Makefile.am create mode 100644 README.txt create mode 100644 configure.ac create mode 100755 entrypoint.sh create mode 100644 init/Makefile.am create mode 100755 init/remove-nginx.sh create mode 100755 init/remove-systemd.sh create mode 100755 init/remove.sh create mode 100755 init/setup-nginx.sh create mode 100755 init/setup-systemd.sh create mode 100755 init/setup.sh create mode 100644 m4/m4_ax_cxx_compile_stdcxx.m4 create mode 100644 rss/Makefile.am create mode 100644 rss/inc/add.h create mode 100644 rss/inc/default.h create mode 100644 rss/inc/escape.h create mode 100644 rss/inc/info.h create mode 100644 rss/inc/log.h create mode 100644 rss/inc/main.h create mode 100644 rss/inc/meta.h create mode 100644 rss/inc/next.h create mode 100644 rss/inc/opt.h create mode 100644 rss/inc/rss.h create mode 100644 rss/inc/util.h create mode 100644 rss/src/add.c create mode 100644 rss/src/default.c create mode 100644 rss/src/escape.c create mode 100644 rss/src/info.c create mode 100644 rss/src/log.c create mode 100644 rss/src/main.c create mode 100644 rss/src/meta.c create mode 100644 rss/src/next.c create mode 100644 rss/src/opt/desc.c create mode 100644 rss/src/opt/feed.c create mode 100644 rss/src/opt/link.c create mode 100644 rss/src/opt/meta.c create mode 100644 rss/src/opt/name.c create mode 100644 rss/src/opt/out.c create mode 100644 rss/src/util/now.c create mode 100644 rss/test/add.tests.c create mode 100644 rss/test/add.tests.h create mode 100644 rss/test/escape.tests.c create mode 100644 rss/test/escape.tests.h create mode 100644 rss/test/info.tests.c create mode 100644 rss/test/info.tests.h create mode 100644 rss/test/meta.tests.c create mode 100644 rss/test/meta.tests.h create mode 100644 rss/test/next.tests.c create mode 100644 rss/test/next.tests.h create mode 100644 rss/test/opt.desc.tests.c create mode 100644 rss/test/opt.desc.tests.h create mode 100644 rss/test/opt.feed.tests.c create mode 100644 rss/test/opt.feed.tests.h create mode 100644 rss/test/opt.link.tests.c create mode 100644 rss/test/opt.link.tests.h create mode 100644 rss/test/opt.meta.tests.c create mode 100644 rss/test/opt.meta.tests.h create mode 100644 rss/test/opt.name.tests.c create mode 100644 rss/test/opt.name.tests.h create mode 100644 rss/test/opt.out.tests.c create mode 100644 rss/test/opt.out.tests.h create mode 100644 rss/test/test_utils.c create mode 100644 rss/test/test_utils.h create mode 100644 rss/test/util.now.tests.c create mode 100644 rss/test/util.now.tests.h create mode 100644 seederd/Makefile.am create mode 100644 seederd/inc/consts.h create mode 100644 seederd/inc/feed.hpp create mode 100644 seederd/inc/main.h create mode 100644 seederd/inc/seed.hpp create mode 100644 seederd/inc/torrent.hpp create mode 100644 seederd/src/feed.cpp create mode 100644 seederd/src/main.cpp create mode 100644 seederd/src/seed.cpp create mode 100644 seederd/src/torrent.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92947ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# autoconf/make +autom4te.cache/ +autoscan.log +aclocal.m4 +build-aux/ +config.h.in +config.h +config.h.in~ +config.log +config.status +configure +configure.scan +*.in +*.in~ +**/.deps +**/.dirstamp +**/Makefile +**/Makefile.in +stamp-h1 + +# build objects +*.o +*.log +*.trs +*.tests + +# binaries +rss/rss-create +rss/*_tests +seederd/seederd diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 31d55c5..0000000 --- a/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM debian:latest - -ENTRYPOINT bash diff --git a/Makefile b/Makefile deleted file mode 100644 index d0f616a..0000000 --- a/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -IMAGE_NAME=seeder -CONTAINER_NAME=seeder-test -build: - docker build -t $(IMAGE_NAME) . - -run: build - docker run -d \ - -p "8080:8080" \ - --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ - --name $(CONTAINER_NAME) \ - $(IMAGE_NAME) - -start: build - -docker run -it --rm \ - --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ - -p "8080:8080" \ - $(IMAGE_NAME) - -stop: - docker stop $(CONTAINER_NAME) - docker rm $(CONTAINER_NAME) - diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..eac46e1 --- /dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = rss seederd init diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..eb30f61 --- /dev/null +++ b/README.txt @@ -0,0 +1,24 @@ +Compile-Dependencies: +libtorrent-rasterbar-dev +inotify-tools + +Optional Dependencies: +systemd +nginx-light + +Build Dependencies: +autoconf +automake +gcc +g++ + +Testing Dependencies: +valgrind + +To Build+Install: +autoreconf -vif +./configure +make +make install +systemctl enable seederd +systemctl start seederd diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..29dfe87 --- /dev/null +++ b/configure.ac @@ -0,0 +1,83 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.69]) +AC_INIT([seeder], [0.0.0]) + +# Store build files not in main directory +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) + +AM_INIT_AUTOMAKE([foreign subdir-objects nostdinc -Wall -Werror]) + +AC_ARG_ENABLE([tests], + [AS_HELP_STRING([--disable-tests], + [disable tests (enabled by default)])], + [enable_tests=$enableval], + [enable_tests=yes]) + +AC_ARG_ENABLE([memcheck], + [AS_HELP_STRING([--disable-memcheck], + [disable valgrind (enabled by default)])], + [enable_memcheck=$enableval], + [enable_memcheck=yes]) + +AC_PATH_PROG([VALGRIND], [valgrind]) +AM_CONDITIONAL([HAVE_VALGRIND], [test -n "$VALGRIND"]) +AC_PATH_PROG([NGINX], [nginx]) +AM_CONDITIONAL([HAVE_NGINX], [test -n "$NGINX"]) +AC_PATH_PROG([SYSTEMD], [systemd]) +AM_CONDITIONAL([HAVE_SYSTEMD], [test -n "$SYSTEMD"]) + +AC_PATH_PROG([INOTIFYWAIT], [inotifywait]) +if test -z "$INOTIFYWAIT"; then + AC_MSG_ERROR("inotifywait required"); +fi + +dnl disable memcheck if valgrind not found +if test "x$enable_memcheck" != "xno"; then + if test -z "$VALGRIND"; then + enable_memcheck=no + fi +fi + +AC_MSG_CHECKING([if memcheck should be enabled]) +if test x$enable_memcheck != xno; then + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +AC_ARG_VAR(SEEDER_DOMAIN,[hostname from which seeder is serving]) + +AM_CONDITIONAL([ENABLE_MEMCHECK],[test x$enable_memcheck = xyes]) + + +# Checks for programs. +AC_PROG_CC +AC_PROG_CXX +AC_PROG_LN_S + +# Checks for libraries. +AC_CHECK_LIB([torrent-rasterbar], [main],,AC_MSG_ERROR(libtorrent-rasterbar required)) +AC_CHECK_LIB([boost_system], [main],,AC_MSG_ERROR(boost_system required)) +AC_CHECK_LIB([pthread], [main],,AC_MSG_ERROR(pthread required)) + +# Checks for header files. +AC_CHECK_HEADERS([stddef.h stdlib.h string.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_CHECK_HEADER_STDBOOL +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_CHECK_FUNCS([atexit mkdir strchr strpbrk strrchr]) + +AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory]) + +AC_CONFIG_FILES([Makefile + init/Makefile + rss/Makefile + seederd/Makefile]) +AC_OUTPUT diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..3d53fcb --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail + +rm /etc/ssh/ssh_host_* +dpkg-reconfigure openssh-server + +service ssh start +service nginx start +service cron start + +monitor-seederd & + +seederd diff --git a/init/Makefile.am b/init/Makefile.am new file mode 100644 index 0000000..f21f601 --- /dev/null +++ b/init/Makefile.am @@ -0,0 +1,17 @@ +install-exec-local: + ./setup.sh $(bindir) $(localstatedir)/seeder $(SEEDER_DOMAIN) +if HAVE_NGINX + ./setup-nginx.sh $(localstatedir)/seeder +endif +if HAVE_SYSTEMD + -./setup-systemd.sh $(localstatedir)/seeder +endif + +uninstall-local: + -./remove.sh $(bindir) $(localstatedir)/seeder +if HAVE_NGINX + -./remove-nginx.sh $(localstatedir)/seeder +endif +if HAVE_SYSTEMD + -./remove-systemd.sh +endif diff --git a/init/remove-nginx.sh b/init/remove-nginx.sh new file mode 100755 index 0000000..3bc3470 --- /dev/null +++ b/init/remove-nginx.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +rm -f "$1/index.html" + +rm -f /etc/nginx/sites-enabled/feeds +rm -f /etc/nginx/sites-available/feeds + +systemctl reload nginx diff --git a/init/remove-systemd.sh b/init/remove-systemd.sh new file mode 100755 index 0000000..1ab32d8 --- /dev/null +++ b/init/remove-systemd.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +systemctl stop seederd +systemctl disable seederd +rm -f /lib/systemd/system/seederd.service + +systemctl stop seederd-monitor +systemctl disable seederd-monitor +rm -f /lib/systemd/system/seederd-monitor.service + +systemctl daemon-reload +systemctl reset-failed diff --git a/init/remove.sh b/init/remove.sh new file mode 100755 index 0000000..48f00e7 --- /dev/null +++ b/init/remove.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +rm -f "$1/update-feeds" + +rm -rf "$2" diff --git a/init/setup-nginx.sh b/init/setup-nginx.sh new file mode 100755 index 0000000..c07d180 --- /dev/null +++ b/init/setup-nginx.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +set -euo pipefail + +mkdir -p "$1" + +cat > "$1/index.html" << EOF + +

This is the homepage of seeder.

+

Feeds

+

Torrent Files

+ +EOF + +# setup nginx +rm -f /etc/nginx/sites-enabled/default +cat > /etc/nginx/sites-available/feeds << EOF +server { + listen 80 default_server; + listen [::]:80; + + root $1; + index index.html; + + location /feeds { + autoindex on; + } + + location /torrents { + autoindex on; + } +} +EOF +ln -sf /etc/nginx/sites-available/feeds /etc/nginx/sites-enabled/feeds + +set +e; systemctl reload nginx; set -e; diff --git a/init/setup-systemd.sh b/init/setup-systemd.sh new file mode 100755 index 0000000..917a7ea --- /dev/null +++ b/init/setup-systemd.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cat > /lib/systemd/system/seederd.service << EOF +[Unit] +Description=Seeder daemon +After=network.target + +[Service] +ExecStartPre=mkdir -p $1 +ExecStart=seederd +ExecReload=kill -HUP \$MAINPID + +Type=simple +Restart=always +TimeoutStopSec=60 + +# Hardening measures +PrivateTmp=true +ProtectSystem=full +ProtectHome=true +NoNewPrivileges=true +PrivateDevices=true +ReadWritePaths=$1 + +[Install] +WantedBy=multi-user.target +EOF + +cat > /lib/systemd/system/seederd-monitor.service << EOF +[Unit] +Description=Seeder Monitor Service +After=seederd.service + +[Service] +ExecStartPre=mkdir -p "$1/data" +ExecStart=inotifywait -e modify,create,delete,move -r "$1/data" +ExecStopPost=systemctl reload seederd +Type=simple +Restart=on-success +RestartSec=500ms + +[Install] +RequiredBy=seederd.service +EOF diff --git a/init/setup.sh b/init/setup.sh new file mode 100755 index 0000000..38f3e6b --- /dev/null +++ b/init/setup.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -euo pipefail + +mkdir -p "$1" +mkdir -p "$2" + +cat > "$1/update-feeds" << EOF +#!/usr/bin/env bash + +set -euo pipefail + +mkdir -p $2/feeds + +FEEDS=(\$(find $2/data -maxdepth 1 -mindepth 1 -type d)) + +for feed in "\${FEEDS[@]}"; do + NAME=\$(basename \$feed) + echo "building feed: \$NAME" + (cd \$feed; find . -mindepth 1 -maxdepth 1 -not -name ".*" -not -path "**/.*" | sed "s|./||" | sort | rss-create -o $2/feeds/\$NAME.xml -l "$3" -f "$3/torrents/" -n \$NAME -m "../.\$NAME.meta") +done +EOF + +chmod +x "$1/update-feeds" diff --git a/m4/m4_ax_cxx_compile_stdcxx.m4 b/m4/m4_ax_cxx_compile_stdcxx.m4 new file mode 100644 index 0000000..9413da6 --- /dev/null +++ b/m4/m4_ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,962 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) +# or '14' (for the C++14 standard). +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 12 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L + +]]) diff --git a/rss/Makefile.am b/rss/Makefile.am new file mode 100644 index 0000000..2602499 --- /dev/null +++ b/rss/Makefile.am @@ -0,0 +1,126 @@ +AM_CPPFLAGS = \ + -I$(builddir)/inc/ \ + -I$(srcdir)/inc/ + +bin_PROGRAMS = rss-create + +rss_create_SOURCES = \ + src/add.c \ + src/default.c \ + src/escape.c \ + src/info.c \ + src/log.c \ + src/main.c \ + src/meta.c \ + src/next.c \ + src/opt/desc.c \ + src/opt/feed.c \ + src/opt/link.c \ + src/opt/meta.c \ + src/opt/name.c \ + src/opt/out.c \ + src/util/now.c + +rss_create_SOURCES += \ + inc/add.h \ + inc/default.h \ + inc/escape.h \ + inc/info.h \ + inc/log.h \ + inc/main.h \ + inc/meta.h \ + inc/next.h \ + inc/opt.h \ + inc/util.h + +EXTRA_DIST = \ + test/add.tests.h \ + test/escape.tests.h \ + test/info.tests.h \ + test/meta.tests.h \ + test/next.tests.h \ + test/opt.desc.tests.h \ + test/opt.feed.tests.h \ + test/opt.link.tests.h \ + test/opt.meta.tests.h \ + test/opt.name.tests.h \ + test/opt.out.tests.h \ + test/util.now.tests.h \ + test/test_utils.h + +check_PROGRAMS = add_tests escape_tests info_tests meta_tests next_tests opt_description_tests opt_feed_tests opt_link_tests opt_meta_tests opt_name_tests opt_out_tests util_now_tests + +TESTS = $(check_PROGRAMS) + +if ENABLE_MEMCHECK +LOG_COMPILER = $(VALGRIND) +AM_LOG_FLAGS = --leak-check=full -v --track-origins=yes --error-exitcode=1 +endif + +common_SOURCES = \ + src/default.c \ + src/log.c \ + src/opt/out.c \ + src/util/now.c \ + test/test_utils.c + +add_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + src/meta.c \ + test/add.tests.c + +escape_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + test/escape.tests.c + +info_tests_SOURCES = \ + $(common_SOURCES) \ + src/info.c \ + test/info.tests.c + +meta_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + src/meta.c \ + test/meta.tests.c + +next_tests_SOURCES = \ + $(common_SOURCES) \ + test/next.tests.c + +opt_description_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/desc.c \ + test/opt.desc.tests.c + +opt_feed_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/feed.c \ + test/opt.feed.tests.c + +opt_link_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/link.c \ + test/opt.link.tests.c + +opt_meta_tests_SOURCES = \ + $(common_SOURCES) \ + src/escape.c \ + src/meta.c \ + src/opt/meta.c \ + test/opt.meta.tests.c + +opt_name_tests_SOURCES = \ + $(common_SOURCES) \ + src/opt/name.c \ + test/opt.name.tests.c + +opt_out_tests_SOURCES = \ + $(common_SOURCES) \ + test/opt.out.tests.c + +util_now_tests_SOURCES = \ + $(common_SOURCES) \ + test/util.now.tests.c diff --git a/rss/inc/add.h b/rss/inc/add.h new file mode 100644 index 0000000..2324c22 --- /dev/null +++ b/rss/inc/add.h @@ -0,0 +1,25 @@ +#ifndef __ADD_H_ +#define __ADD_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#define FIELD_ADDER(x) add_field(x,FIELD_PREFIX(FIELD_STRING_LOADER(x)),FIELD_SUFFIX(FIELD_STRING_LOADER(x))) + +#define add_title() FIELD_ADDER(FIELD_TITLE) +#define add_link() FIELD_ADDER(FIELD_LINK) +#define add_pubdate() FIELD_ADDER(FIELD_PUBDATE) +#define add_description() FIELD_ADDER(FIELD_DESCRIPTION) +#define add_guid() FIELD_ADDER(FIELD_GUID) + +int add(); +int add_default(enum field); +int add_field(enum field,const char*,const char*); + +#endif diff --git a/rss/inc/default.h b/rss/inc/default.h new file mode 100644 index 0000000..4fe7fa9 --- /dev/null +++ b/rss/inc/default.h @@ -0,0 +1,14 @@ +#ifndef __DEFAULT_H_ +#define __DEFAULT_H_ + +#include +#include + +#include +#include +#include +#include + +int defaults(); + +#endif diff --git a/rss/inc/escape.h b/rss/inc/escape.h new file mode 100644 index 0000000..8b6f24a --- /dev/null +++ b/rss/inc/escape.h @@ -0,0 +1,11 @@ +#ifndef __ESCAPE_H_ +#define __ESCAPE_H_ + +#include +#include + +#include + +int escape(char*,size_t); + +#endif diff --git a/rss/inc/info.h b/rss/inc/info.h new file mode 100644 index 0000000..f6ed0cb --- /dev/null +++ b/rss/inc/info.h @@ -0,0 +1,13 @@ +#ifndef __INFO_H_ +#define __INFO_H_ + +#include +#include +#include + +#define INFO_DEFAULT_DESCRIPTION "(no description)" +#define INFO_DEFAULT_LANGUAGE "en-us" + +int info(); + +#endif diff --git a/rss/inc/log.h b/rss/inc/log.h new file mode 100644 index 0000000..dfd9c09 --- /dev/null +++ b/rss/inc/log.h @@ -0,0 +1,15 @@ +#ifndef __LOG_H_ +#define __LOG_H_ + +#include +#include + +extern FILE *output_stream; + +#define log_err(...) log_message(stderr,__VA_ARGS__) +#define out(...) log_message(output_stream,__VA_ARGS__); + +void log_close(); +void log_message(FILE*,const char*,...); + +#endif diff --git a/rss/inc/main.h b/rss/inc/main.h new file mode 100644 index 0000000..504c00c --- /dev/null +++ b/rss/inc/main.h @@ -0,0 +1,15 @@ +#ifndef __MAIN_H_ +#define __MAIN_H_ + +#include +#include +#include + +#include +#include +#include +#include + +int main(int,char**); + +#endif diff --git a/rss/inc/meta.h b/rss/inc/meta.h new file mode 100644 index 0000000..b43fc56 --- /dev/null +++ b/rss/inc/meta.h @@ -0,0 +1,15 @@ +#ifndef __META_H_ +#define __META_H_ + +#include + +#include +#include +#include +#include +#include + +int meta(char*,char[FIELD_COUNT][BUF_SIZE]); +int meta_filename(char*,char*,size_t); + +#endif diff --git a/rss/inc/next.h b/rss/inc/next.h new file mode 100644 index 0000000..7125b6c --- /dev/null +++ b/rss/inc/next.h @@ -0,0 +1,10 @@ +#ifndef __NEXT_H_ +#define __NEXT_H_ + +#include + +#define BUF_SIZE 4096 + +int next(char*,size_t); + +#endif diff --git a/rss/inc/opt.h b/rss/inc/opt.h new file mode 100644 index 0000000..bf000a9 --- /dev/null +++ b/rss/inc/opt.h @@ -0,0 +1,35 @@ +#ifndef __OPT_H_ +#define __OPT_H_ + +#include +#include +#include +#include + +#include +#include +#include + +struct channel_options { + char *title; + char *link; + char *description; + char *language; + char *last_build_date; +}; + +struct global_options { + struct channel_options channel; + char *feedurl; +}; + +extern struct global_options global_opts; + +int opt_set_and_parse_meta(char*); +int opt_set_description(char*); +int opt_set_feedurl(char*); +int opt_set_link(char*); +int opt_set_name(char*); +int opt_set_output(char*); + +#endif diff --git a/rss/inc/rss.h b/rss/inc/rss.h new file mode 100644 index 0000000..97c20cd --- /dev/null +++ b/rss/inc/rss.h @@ -0,0 +1,29 @@ +#ifndef __RSS_H_ +#define __RSS_H_ + +enum field { + FIELD_TITLE = 0, + FIELD_LINK, + FIELD_PUBDATE, + FIELD_DESCRIPTION, + FIELD_GUID, + FIELD_LANGUAGE, + FIELD_COUNT +}; + +#define XML_START "<" +#define XML_CLOSE "" + +#define FIELD_TITLE_STRING "title" +#define FIELD_LINK_STRING "link" +#define FIELD_PUBDATE_STRING "pubDate" +#define FIELD_DESCRIPTION_STRING "description" +#define FIELD_GUID_STRING "guid" +#define FIELD_LANGUAGE_STRING "language" + +#define FIELD_STRING_LOADER(x) x ## _STRING +#define FIELD_PREFIX(x) XML_START x XML_END +#define FIELD_SUFFIX(x) XML_CLOSE x XML_END + +#endif diff --git a/rss/inc/util.h b/rss/inc/util.h new file mode 100644 index 0000000..448c56d --- /dev/null +++ b/rss/inc/util.h @@ -0,0 +1,9 @@ +#ifndef __UTIL_H_ +#define __UTIL_H_ + +#include +#include + +int now_string(char**); + +#endif diff --git a/rss/src/add.c b/rss/src/add.c new file mode 100644 index 0000000..e0b9e2d --- /dev/null +++ b/rss/src/add.c @@ -0,0 +1,65 @@ +#include + +char bufs[FIELD_COUNT][BUF_SIZE] = {0}; + +int add() { + int i; + + // clear bufs + for(i=0;i"); + if(add_title()<0) { return -1; } + if(add_link()<0) { return -1; } + if(add_pubdate()<0) { return -1; } + if(add_description()<0) { return -1; } + if(add_guid()<0) { return -1; } + out(""); + return 1; +} + +int add_field(enum field field, const char *prefix, const char *suffix) { + if(strlen(bufs[field])>0) { + out("%s",prefix); + out("%s",bufs[field]); + out("%s",suffix); + } else { + if(add_default(field)<0) { return -1; } + } + + return 1; +} + +int add_default(enum field type) { + char *timebuf; + switch(type) { + case FIELD_TITLE: + case FIELD_LINK: + log_err("missing required field...aborting...\n"); + return -1; + case FIELD_DESCRIPTION: + out("no description"); + break; + case FIELD_GUID: + break; + case FIELD_PUBDATE: + if(now_string(&timebuf)<0) { return -1; } + out("%s",timebuf); + free(timebuf); + break; + default: + log_err("unknown field type\n"); + return -1; + } + + return 1; +} diff --git a/rss/src/default.c b/rss/src/default.c new file mode 100644 index 0000000..860d6a3 --- /dev/null +++ b/rss/src/default.c @@ -0,0 +1,16 @@ +#include + +struct global_options global_opts = { { NULL }, NULL }; + +int defaults() { + struct channel_options *ch; + + output_stream = stdout; + if(atexit(log_close)!=0) { return -1; } + + ch = &(global_opts.channel); + + if(now_string(&(global_opts.channel.last_build_date))<0) { return -1; } + + return 1; +} diff --git a/rss/src/escape.c b/rss/src/escape.c new file mode 100644 index 0000000..2e7a3f9 --- /dev/null +++ b/rss/src/escape.c @@ -0,0 +1,84 @@ +#include + +int escape(char *buf, size_t buf_size) { + char *p, *p2, *tmpbuf; + char c; + size_t len; + int left; + + if(NULL==buf) { return -1; } + if(buf_size<=0) { return -1; } + if(strlen(buf)>buf_size) { return -1; } + + tmpbuf = NULL; + + char to_escape[] = "<\\>&"; + + p = strpbrk(buf,to_escape); + p2 = buf; + while(NULL!=p) { + if(NULL==tmpbuf) { + tmpbuf = malloc(buf_size); + if(NULL==tmpbuf) { + log_err("allocation error\n"); + return -1; + } + tmpbuf[0] = '\0'; + } + + c = p[0]; + p[0] = '\0'; + p++; + + strcat(tmpbuf,p2); + + left = buf_size - strlen(tmpbuf); + + switch(c) { + case '<': + if(left<=4) { goto panic; } + strcat(tmpbuf,"<"); + break; + case '\\': + if(p[0]=='n') { + if(left<1) { goto panic; } + strcat(tmpbuf,"\n"); + p++; + } + break; + case '>': + if(left<=4) { goto panic; } + strcat(tmpbuf,">"); + break; + case '&': + if(left<=5) { goto panic; } + strcat(tmpbuf,"&"); + break; + default: + log_err("unknown escape character: %c\n",c); + goto panic; + } + + p2 = p; + p = strpbrk(p,to_escape); + } + + if(tmpbuf!=NULL) { + strcat(tmpbuf,p2); + len = strlen(tmpbuf); + if(len>buf_size) { + log_err("escaped string larger than buffer...\n"); + goto panic; + } + + strcpy(buf,tmpbuf); + free(tmpbuf); + } + + return 1; + panic: + if(tmpbuf!=NULL) { + free(tmpbuf); + } + return -1; +} diff --git a/rss/src/info.c b/rss/src/info.c new file mode 100644 index 0000000..8dc9d5e --- /dev/null +++ b/rss/src/info.c @@ -0,0 +1,36 @@ +#include + +int info() { + struct channel_options *ch; + + ch = &(global_opts.channel); + + if(NULL==ch->title) { + log_err("missing channel title\n"); + return -1; + } + + if(NULL==ch->link) { + log_err("missing channel link\n"); + return -1; + } + + out("%s",global_opts.channel.title); + out("%s",global_opts.channel.link); + + if(NULL==ch->description) { + out("%s",INFO_DEFAULT_DESCRIPTION); + } else { + out("%s",ch->description); + } + + if(NULL==ch->language) { + out("%s",INFO_DEFAULT_LANGUAGE); + } else { + out("%s",global_opts.channel.language); + } + + out("%s",global_opts.channel.last_build_date); + + return 1; +} diff --git a/rss/src/log.c b/rss/src/log.c new file mode 100644 index 0000000..1a6da1b --- /dev/null +++ b/rss/src/log.c @@ -0,0 +1,16 @@ +#include + +FILE *output_stream; + +void log_close() { + if(output_stream!=stdout) { + fclose(output_stream); + } +} + +void log_message(FILE *out_stream, const char *format,...) { + va_list args; + va_start(args,format); + vfprintf(out_stream,format,args); + va_end(args); +} diff --git a/rss/src/main.c b/rss/src/main.c new file mode 100644 index 0000000..cf82e47 --- /dev/null +++ b/rss/src/main.c @@ -0,0 +1,69 @@ +#include + +static struct option long_options[] = { + {"description", required_argument, 0, 'd'}, + {"feedurl", required_argument, 0, 'f'}, + {"link", required_argument, 0, 'l'}, + {"meta", required_argument, 0, 'm'}, + {"name", required_argument, 0, 'n'}, + {"output", required_argument, 0, 'o'}, + {0,0,0,0} +}; + +int main(int argc, char **argv) { + int c,i; + + if(defaults()<0) { return EXIT_FAILURE; } + + while(1) { + int option_index = 0; + + if((c = getopt_long(argc,argv,"d:f:l:m:n:o:",long_options,&option_index))==-1) { break; } + switch(c) { + case 0: + if(long_options[option_index].flag!=0) { break; } + log_err("option %s", long_options[option_index].name); + if(optarg) { + log_err(" with arg %s",optarg); + } + log_err("\n"); + return EXIT_FAILURE; + + break; + case 'd': + if(opt_set_description(optarg)<0) { return EXIT_FAILURE; } + break; + case 'f': + if(opt_set_feedurl(optarg)<0) { return EXIT_FAILURE; } + break; + case 'l': + if(opt_set_link(optarg)<0) { return EXIT_FAILURE; } + break; + case 'm': + if(opt_set_and_parse_meta(optarg)<0) { return EXIT_FAILURE; } + break; + case 'n': + if(opt_set_name(optarg)<0) { return EXIT_FAILURE; } + break; + case 'o': + if(opt_set_output(optarg)<0) { return EXIT_FAILURE; } + break; + case '?': + default: + return EXIT_FAILURE; + } + } + + out(""); + out(""); + + if(info()<0) { return EXIT_FAILURE; } + + while((i = add())>0) { } + + if(i<0) { return EXIT_FAILURE; } + + out("\n"); + + return EXIT_SUCCESS; +} diff --git a/rss/src/meta.c b/rss/src/meta.c new file mode 100644 index 0000000..9cb2bdc --- /dev/null +++ b/rss/src/meta.c @@ -0,0 +1,100 @@ +#include + +int meta_filename(char *file, char *out, size_t out_len) { + char *p; + size_t len; + + p = strrchr(file,'/'); + if(NULL==p) { + p = file; + } else { + p++; + } + + len = p-file; + // prefix length, "." [1], suffix length, ".meta", '\0' + if((len+1+strlen(p)+5+1)>out_len) { + return -1; + } + + if(len>0) { + strncpy(out,file,len); + } + + out[len] = '.'; + out[len+1] = '\0'; + strcat(out,p); + strcat(out,".meta"); + + return 1; +} + +int meta(char *meta_file, char bufs[FIELD_COUNT][BUF_SIZE]) { + char tmp[BUF_SIZE]; + char *p,*p2; + FILE *fp; + int i; + + if(NULL==meta_file) { + if(meta_filename(bufs[FIELD_TITLE],tmp,BUF_SIZE)<0) { return -1; } + fp = fopen(tmp,"r"); + } else { + fp = fopen(meta_file,"r"); + bufs[FIELD_TITLE][0] = '\0'; + } + + if(NULL==fp) { return 0; } + + // FIELD_TITLE=0 + for(i=1;i + +int next(char *buf, size_t buf_size) { + int i; + + if(NULL==buf) { return -1; } + + i = 0; + while((buf[i] = getchar())!=EOF) { + if(buf[i]=='\n') { break; } + if(i>=buf_size) { return -1; } + i++; + } + + buf[i] = '\0'; + + return (i>0); +} diff --git a/rss/src/opt/desc.c b/rss/src/opt/desc.c new file mode 100644 index 0000000..00129df --- /dev/null +++ b/rss/src/opt/desc.c @@ -0,0 +1,19 @@ +#include + +int opt_set_description(char *desc) { + char *p; + size_t len; + + if(NULL==desc) { return -1; } + + len = strlen(desc); + if(len==0) { return -1; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,desc); + global_opts.channel.description = p; + + return 1; +} diff --git a/rss/src/opt/feed.c b/rss/src/opt/feed.c new file mode 100644 index 0000000..321e084 --- /dev/null +++ b/rss/src/opt/feed.c @@ -0,0 +1,26 @@ +#include + +int opt_set_feedurl(char *url) { + char *p; + size_t len; + + if(NULL==url) { return -1; } + + len = strlen(url); + if(len==0) { return -1; } + + if(url[len-1]!='/') { len++; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,url); + + if(url[len-1]!='/') { + strcat(p,"/"); + } + + global_opts.feedurl = p; + + return 1; +} diff --git a/rss/src/opt/link.c b/rss/src/opt/link.c new file mode 100644 index 0000000..8d2d4e1 --- /dev/null +++ b/rss/src/opt/link.c @@ -0,0 +1,19 @@ +#include + +int opt_set_link(char *link) { + char *p; + size_t len; + + if(NULL==link) { return -1; } + + len = strlen(link); + if(len==0) { return -1; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,link); + global_opts.channel.link = p; + + return 1; +} diff --git a/rss/src/opt/meta.c b/rss/src/opt/meta.c new file mode 100644 index 0000000..cc7e21d --- /dev/null +++ b/rss/src/opt/meta.c @@ -0,0 +1,74 @@ +#include + +int opt_set_and_parse_meta(char *filename) { + char bufs[FIELD_COUNT][BUF_SIZE] = {0}; + char *p; + size_t len, title_len, desc_len, link_len, guid_len, lan_len; + struct channel_options *ch; + int i; + + if(NULL==filename) { return -1; } + + if((i = meta(filename,bufs))<0) { return -1; } + if(i==0) { + log_err("missing meta file: %s\n",filename); + return -1; + } + + ch = &(global_opts.channel); + + title_len = strlen(bufs[FIELD_TITLE]); + desc_len = strlen(bufs[FIELD_DESCRIPTION]); + link_len = strlen(bufs[FIELD_LINK]); + guid_len = strlen(bufs[FIELD_GUID]); + lan_len = strlen(bufs[FIELD_LANGUAGE]); + len = title_len+desc_len+link_len+guid_len+lan_len+5; + + if(len>0) { + p = malloc(sizeof(char)*(len)); + if(NULL==p) { return -1; } + } + + if(title_len>0) { + ch->title = p; + strcpy(ch->title,bufs[FIELD_TITLE]); + p += title_len+1; + } + + if(desc_len>0) { + ch->description = p; + strcpy(ch->description,bufs[FIELD_DESCRIPTION]); + p += desc_len+1; + } + + if(link_len>0) { + if(NULL==ch->description) { + ch->description = p; + ch->description[0] = '\0'; + } else { + strcat(ch->description,"\n"); + } + + strcat(ch->description,bufs[FIELD_LINK]); + p += link_len+1; + } + + if(guid_len>0) { + if(NULL==ch->description) { + ch->description = p; + ch->description[0] = '\0'; + } else { + strcat(ch->description,"\n"); + } + + strcat(ch->description,bufs[FIELD_GUID]); + p += guid_len+1; + } + + if(lan_len>0) { + ch->language = p; + strcpy(ch->language,bufs[FIELD_LANGUAGE]); + } + + return 1; +} diff --git a/rss/src/opt/name.c b/rss/src/opt/name.c new file mode 100644 index 0000000..9883836 --- /dev/null +++ b/rss/src/opt/name.c @@ -0,0 +1,19 @@ +#include + +int opt_set_name(char *name) { + char *p; + size_t len; + + if(NULL==name) { return -1; } + + len = strlen(name); + if(len==0) { return -1; } + + p = malloc(sizeof(char)*(len+1)); + if(NULL==p) { return -1; } + + strcpy(p,name); + global_opts.channel.title = p; + + return 1; +} diff --git a/rss/src/opt/out.c b/rss/src/opt/out.c new file mode 100644 index 0000000..7d1956d --- /dev/null +++ b/rss/src/opt/out.c @@ -0,0 +1,16 @@ +#include + +int opt_set_output(char *target) { + FILE *fp; + + if(NULL==target) { return -1; } + + fp = fopen(target,"w"); + if(NULL==fp) { + log_err("unable to open %s for writing\n",target); + return -1; + } + + output_stream = fp; + return 1; +} diff --git a/rss/src/util/now.c b/rss/src/util/now.c new file mode 100644 index 0000000..b761e07 --- /dev/null +++ b/rss/src/util/now.c @@ -0,0 +1,15 @@ +#include + +int now_string(char **timebuf) { + if(NULL==timebuf) { return -1; } + + *timebuf = malloc(sizeof(char)*70); + if(NULL==(*timebuf)) { return -1; } + + struct tm now; + + now = *localtime(&(time_t){time(NULL)}); + if(!(strftime((*timebuf),sizeof(char)*70, "%a, %d %b %Y %H:%M:%S %z", &now))) { return -1; } + + return 1; +} diff --git a/rss/test/add.tests.c b/rss/test/add.tests.c new file mode 100644 index 0000000..513fbb0 --- /dev/null +++ b/rss/test/add.tests.c @@ -0,0 +1,52 @@ +#include "add.tests.h" + +int main() { + setup_env(); + + add_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +static int i = 0; + +void add_basic_tests() { + FILE *fp; + char buf[1024]; + + assert(opt_set_output(DATADIR "feed.xml")==1); + + assert(add()==1); + assert(add()==-1); + + log_close(); + + fp = fopen(DATADIR "feed.xml","r"); + assert(NULL!=fp); + + assert(fread(buf,sizeof(char),1024,fp)==260); + assert(fclose(fp)==0); + + assert(memcmp(buf,"/tmp/rss-create/test/test2.txt0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrentSat, 15 May 2021 11:01:04 +0000no description/tmp/rss-create/test/test.txt",260)==0); +} + +int next_dummy(char *buf, size_t size) { + switch(i) { + case 0: + snprintf(buf,size,FILE_2); + break; + case 1: + snprintf(buf,size,FILE_1); + break; + case 2: + snprintf(buf,size,FILE_3); + break; + default: + snprintf(buf,size,"notarealfile"); + break; + } + i++; + return 1; +} diff --git a/rss/test/add.tests.h b/rss/test/add.tests.h new file mode 100644 index 0000000..d3985f7 --- /dev/null +++ b/rss/test/add.tests.h @@ -0,0 +1,15 @@ +#ifndef __ADD_TESTS_H_ +#define __ADD_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void add_basic_tests(); +int next_dummy(char*,size_t); + +#define next(buf,size) next_dummy(buf,size) +#include "../src/add.c" + +#endif diff --git a/rss/test/escape.tests.c b/rss/test/escape.tests.c new file mode 100644 index 0000000..83de5c1 --- /dev/null +++ b/rss/test/escape.tests.c @@ -0,0 +1,55 @@ +#include "escape.tests.h" + +int main() { + setup_env(); + + escape_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +#define BUF_SIZE 1024 +void escape_basic_tests() { + char buf[BUF_SIZE]; + + assert(escape(NULL,0)==-1); + assert(escape(buf,0)==-1); + + strcpy(buf,"<"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,"<")==0); + + strcpy(buf,">"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,">")==0); + + strcpy(buf,"\\n"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,"\n")==0); + + strcpy(buf,"&"); + assert(escape(buf,BUF_SIZE)==1); + assert(strcmp(buf,"&")==0); + + strcpy(buf,"this"); + assert(escape(buf,6)==1); + + strcpy(buf,"this&"); + assert(escape(buf,6)==-1); + + strcpy(buf,"this<"); + assert(escape(buf,6)==-1); + + strcpy(buf,"this>"); + assert(escape(buf,6)==-1); + + strcpy(buf,"this\\n"); + assert(escape(buf,4)==-1); + + strcpy(buf,"lskjdflaksjdfl<>slkdfjlsdf&lsdjkfsldfjsldkfj\\nsldkjfslkdjfk<<<<>>>>"); + assert(escape(buf,10)==-1); + assert(escape(buf,1024)==1); + assert(strcmp(buf,"lskjdflaksjdfl<>slkdfjlsdf&lsdjkfsldfjsldkfj\nsldkjfslkdjfk<<<<>>>>")==0); +} diff --git a/rss/test/escape.tests.h b/rss/test/escape.tests.h new file mode 100644 index 0000000..d2eb9b3 --- /dev/null +++ b/rss/test/escape.tests.h @@ -0,0 +1,11 @@ +#ifndef __ESCAPE_TESTS_H_ +#define __ESCAPE_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void escape_basic_tests(); + +#endif diff --git a/rss/test/info.tests.c b/rss/test/info.tests.c new file mode 100644 index 0000000..9dafb6b --- /dev/null +++ b/rss/test/info.tests.c @@ -0,0 +1,41 @@ +#include "info.tests.h" + +int main() { + setup_env(); + + info_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void info_basic_tests() { + FILE *fp; + char buf[1024]; + char buf2[1024]; + + assert(opt_set_output(DATADIR "feed.xml")==1); + + assert(info()==-1); + + global_opts.channel.title = "test title"; + assert(info()==-1); + + global_opts.channel.link = "https://test.com/test/feed.xml"; + assert(now_string(&(global_opts.channel.last_build_date))==1); + + assert(info()==1); + + log_close(); + + fp = fopen(DATADIR "feed.xml","r"); + assert(NULL!=fp); + + assert(fread(buf,sizeof(char),1024,fp)==199); + assert(fclose(fp)==0); + + sprintf(buf2,"test titlehttps://test.com/test/feed.xml(no description)en-us%s",global_opts.channel.last_build_date); + + assert(memcmp(buf,buf2,199)==0); +} diff --git a/rss/test/info.tests.h b/rss/test/info.tests.h new file mode 100644 index 0000000..8fbc311 --- /dev/null +++ b/rss/test/info.tests.h @@ -0,0 +1,11 @@ +#ifndef __INFO_TESTS_H_ +#define __INFO_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void info_basic_tests(); + +#endif diff --git a/rss/test/meta.tests.c b/rss/test/meta.tests.c new file mode 100644 index 0000000..05f3b8e --- /dev/null +++ b/rss/test/meta.tests.c @@ -0,0 +1,219 @@ +#include "meta.tests.h" + +int main() { + setup_env(); + + meta_basic_tests(); + meta_feedurl_test(); + meta_filename_basic_test(); + meta_duplicate_field_test(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void meta_basic_tests() { + char bufs[FIELD_COUNT][BUF_SIZE]; + + assert(meta("nonexistentfile",bufs)==0); + strcpy(bufs[FIELD_TITLE],"nonexistentfile"); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_1,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_1); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_2,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],FILE_2)==0); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + assert(strcmp(bufs[FIELD_PUBDATE],"Sat, 15 May 2021 11:01:04 +0000")==0); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(FILE_3,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_3); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_4,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(strcmp(bufs[FIELD_PUBDATE],"Sun, 16 May 2021 02:55:07 +0000")==0); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this a<Mactual>ly show\na new line")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_4); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_5,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + assert(strcmp(bufs[FIELD_PUBDATE],"Sat, 15 May 2021 11:01:04 +0000")==0); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_5); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_6,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_6); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_7,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(strcmp(bufs[FIELD_GUID],"hello")==0); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_7); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_8,bufs)==-1); + strcpy(bufs[FIELD_TITLE],FILE_8); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],FILE_8)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(strcmp(bufs[FIELD_GUID],"hello")==0); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(FILE_9,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this is a test description")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + strcpy(bufs[FIELD_TITLE],FILE_9); + assert(meta(NULL,bufs)==0); + + assert(meta(FILE_10,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(strcmp(bufs[FIELD_LANGUAGE],"en-uk")==0); + strcpy(bufs[FIELD_TITLE],FILE_10); + assert(meta(NULL,bufs)==0); + + assert(meta(DIRECTORY_1,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(DIRECTORY_2,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(DIRECTORY_3,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + assert(meta(DIRECTORY_4,bufs)==1); + assert(bufs[FIELD_TITLE][0]=='\0'); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_1); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],DIRECTORY_1)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this is a test description")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],DIRECTORY_2)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(bufs[FIELD_PUBDATE][0]=='\0'); + assert(bufs[FIELD_DESCRIPTION][0]=='\0'); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(strcmp(bufs[FIELD_LANGUAGE],"en-uk")==0); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_3); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],DIRECTORY_3)==0); + assert(bufs[FIELD_LINK][0]=='\0'); + assert(strcmp(bufs[FIELD_PUBDATE],"Sun, 16 May 2021 02:55:07 +0000")==0); + assert(strcmp(bufs[FIELD_DESCRIPTION],"this a<Mactual>ly show\na new line")==0); + assert(bufs[FIELD_GUID][0]=='\0'); + assert(bufs[FIELD_LANGUAGE][0]=='\0'); + + strcpy(bufs[FIELD_TITLE],DIRECTORY_4); + assert(meta(NULL,bufs)==0); +} + +void meta_feedurl_test() { + char bufs[FIELD_COUNT][BUF_SIZE]; + + strcpy(bufs[FIELD_TITLE],FILE_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + assert(meta(FILE_5,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + // opt_set_feedurl() should make sure that feedurl has '/' at end + char testurl[] = "https://example.com/feeds/"; + global_opts.feedurl = testurl; + + strcpy(bufs[FIELD_TITLE],FILE_2); + assert(meta(NULL,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"https://example.com/feeds/0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + assert(meta(FILE_5,bufs)==1); + assert(strcmp(bufs[FIELD_LINK],"https://example.com/feeds/0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent")==0); + + global_opts.feedurl = NULL; +} + +void meta_filename_basic_test() { + char tmp[BUF_SIZE]; + char tmp2[BUF_SIZE]; + + strcpy(tmp,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + assert(meta_filename(tmp,tmp2,10)==-1); + assert(meta_filename(tmp,tmp2,BUF_SIZE)==1); + assert(strcmp(tmp2,".aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.meta")==0); + + strcpy(tmp,"/this/is/a/test/path"); + assert(meta_filename(tmp,tmp2,10)==-1); + assert(meta_filename(tmp,tmp2,26)==-1); + assert(meta_filename(tmp,tmp2,27)==1); + assert(strcmp(tmp2,"/this/is/a/test/.path.meta")==0); +} + +void meta_duplicate_field_test() { + char bufs[FIELD_COUNT][BUF_SIZE]; + + assert(meta(FILE_11,bufs)==1); + assert(strcmp(bufs[FIELD_TITLE],"test2")==0); + assert(strcmp(bufs[FIELD_LINK],"link2")==0); + assert(strcmp(bufs[FIELD_PUBDATE],"date2")==0); + assert(strcmp(bufs[FIELD_DESCRIPTION],"desc2")==0); + assert(strcmp(bufs[FIELD_GUID],"guid2")==0); + assert(strcmp(bufs[FIELD_LANGUAGE],"lan2")==0); +} diff --git a/rss/test/meta.tests.h b/rss/test/meta.tests.h new file mode 100644 index 0000000..e15cbe9 --- /dev/null +++ b/rss/test/meta.tests.h @@ -0,0 +1,14 @@ +#ifndef __META_TESTS_H_ +#define __META_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void meta_basic_tests(); +void meta_feedurl_test(); +void meta_filename_basic_test(); +void meta_duplicate_field_test(); + +#endif diff --git a/rss/test/next.tests.c b/rss/test/next.tests.c new file mode 100644 index 0000000..39c1540 --- /dev/null +++ b/rss/test/next.tests.c @@ -0,0 +1,32 @@ +#include "next.tests.h" + +int main() { + setup_env(); + + next_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +static int i = 0; + +void next_basic_tests() { + char buf[1024]; + + assert(next(NULL,0)==-1); + assert(next(buf,10)==-1); + i = 0; + assert(next(buf,1024)==1); + assert(strcmp(buf,"aaaaaaaaaaaaaaaaaaaa")==0); +} + +int getchar_dummy() { + if(i<20) { + i++; + return 'a'; + } else { + return '\n'; + } +} diff --git a/rss/test/next.tests.h b/rss/test/next.tests.h new file mode 100644 index 0000000..2d3a430 --- /dev/null +++ b/rss/test/next.tests.h @@ -0,0 +1,15 @@ +#ifndef __NEXT_TESTS_H_ +#define __NEXT_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void next_basic_tests(); +int getchar_dummy(); + +#define getchar() getchar_dummy() +#include "../src/next.c" + +#endif diff --git a/rss/test/opt.desc.tests.c b/rss/test/opt.desc.tests.c new file mode 100644 index 0000000..65c49e5 --- /dev/null +++ b/rss/test/opt.desc.tests.c @@ -0,0 +1,19 @@ +#include "opt.desc.tests.h" + +int main() { + setup_env(); + + opt_desc_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_desc_basic_tests() { + assert(opt_set_description(NULL)==-1); + assert(opt_set_description("")==-1); + + assert(opt_set_description("test")==1); + assert(strcmp(global_opts.channel.description,"test")==0); +} diff --git a/rss/test/opt.desc.tests.h b/rss/test/opt.desc.tests.h new file mode 100644 index 0000000..f5642d8 --- /dev/null +++ b/rss/test/opt.desc.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_DESC_TESTS_H_ +#define __OPT_DESC_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_desc_basic_tests(); + +#endif diff --git a/rss/test/opt.feed.tests.c b/rss/test/opt.feed.tests.c new file mode 100644 index 0000000..ad675a6 --- /dev/null +++ b/rss/test/opt.feed.tests.c @@ -0,0 +1,25 @@ +#include "opt.feed.tests.h" + +int main() { + setup_env(); + + opt_feed_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_feed_basic_tests() { + assert(opt_set_feedurl(NULL)==-1); + assert(opt_set_feedurl("")==-1); + + assert(opt_set_feedurl("https://test.com")==1); + assert(strcmp(global_opts.feedurl,"https://test.com/")==0); + + free(global_opts.feedurl); + global_opts.feedurl = NULL; + + assert(opt_set_feedurl("http://test.com/")==1); + assert(strcmp(global_opts.feedurl,"http://test.com/")==0); +} diff --git a/rss/test/opt.feed.tests.h b/rss/test/opt.feed.tests.h new file mode 100644 index 0000000..a0ff551 --- /dev/null +++ b/rss/test/opt.feed.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_FEED_TESTS_H_ +#define __OPT_FEED_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_feed_basic_tests(); + +#endif diff --git a/rss/test/opt.link.tests.c b/rss/test/opt.link.tests.c new file mode 100644 index 0000000..7542289 --- /dev/null +++ b/rss/test/opt.link.tests.c @@ -0,0 +1,19 @@ +#include "opt.link.tests.h" + +int main() { + setup_env(); + + opt_link_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_link_basic_tests() { + assert(opt_set_link(NULL)==-1); + assert(opt_set_link("")==-1); + + assert(opt_set_link("alksdjflasdjf")==1); + assert(strcmp(global_opts.channel.link,"alksdjflasdjf")==0); +} diff --git a/rss/test/opt.link.tests.h b/rss/test/opt.link.tests.h new file mode 100644 index 0000000..b45c32b --- /dev/null +++ b/rss/test/opt.link.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_LINK_TESTS_H_ +#define __OPT_LINK_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_link_basic_tests(); + +#endif diff --git a/rss/test/opt.meta.tests.c b/rss/test/opt.meta.tests.c new file mode 100644 index 0000000..6ca9118 --- /dev/null +++ b/rss/test/opt.meta.tests.c @@ -0,0 +1,21 @@ +#include "opt.meta.tests.h" + +int main() { + setup_env(); + + opt_set_and_parse_meta_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_set_and_parse_meta_basic_tests() { + assert(opt_set_and_parse_meta(NULL)==-1); + assert(opt_set_and_parse_meta("notarealfile")==-1); + + assert(opt_set_and_parse_meta(FILE_9)==1); + assert(NULL==global_opts.channel.title); + assert(strcmp(global_opts.channel.description,"this is a test description")==0); + assert(NULL==global_opts.channel.language); +} diff --git a/rss/test/opt.meta.tests.h b/rss/test/opt.meta.tests.h new file mode 100644 index 0000000..f7ae8e7 --- /dev/null +++ b/rss/test/opt.meta.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_META_TESTS_H_ +#define __OPT_META_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_set_and_parse_meta_basic_tests(); + +#endif diff --git a/rss/test/opt.name.tests.c b/rss/test/opt.name.tests.c new file mode 100644 index 0000000..f40d0d0 --- /dev/null +++ b/rss/test/opt.name.tests.c @@ -0,0 +1,19 @@ +#include "opt.name.tests.h" + +int main() { + setup_env(); + + opt_set_name_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_set_name_basic_tests() { + assert(opt_set_name(NULL)==-1); + assert(opt_set_name("")==-1); + + assert(opt_set_name("feedname")==1); + assert(strcmp(global_opts.channel.title,"feedname")==0); +} diff --git a/rss/test/opt.name.tests.h b/rss/test/opt.name.tests.h new file mode 100644 index 0000000..9d0fc2f --- /dev/null +++ b/rss/test/opt.name.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_NAME_TESTS_H_ +#define __OPT_NAME_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_set_name_basic_tests(); + +#endif diff --git a/rss/test/opt.out.tests.c b/rss/test/opt.out.tests.c new file mode 100644 index 0000000..3389841 --- /dev/null +++ b/rss/test/opt.out.tests.c @@ -0,0 +1,16 @@ +#include "opt.out.tests.h" + +int main() { + setup_env(); + + opt_out_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void opt_out_basic_tests() { + assert(opt_set_output(NULL)==-1); + assert(opt_set_output("/tmp/tempoutfile")==1); +} diff --git a/rss/test/opt.out.tests.h b/rss/test/opt.out.tests.h new file mode 100644 index 0000000..2f3cf65 --- /dev/null +++ b/rss/test/opt.out.tests.h @@ -0,0 +1,11 @@ +#ifndef __OPT_OUT_TESTS_H_ +#define __OPT_OUT_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void opt_out_basic_tests(); + +#endif diff --git a/rss/test/test_utils.c b/rss/test/test_utils.c new file mode 100644 index 0000000..1b2e705 --- /dev/null +++ b/rss/test/test_utils.c @@ -0,0 +1,46 @@ +#include "test_utils.h" + +void clean_env() { + system("rm -rf " DATADIR); +} + +void reset_env() { + clean_env(); + setup_env(); +} + +void setup_env() { + clean_env(); + + create_test_directory(DATADIR); + create_test_directory(DIRECTORY_1); + create_test_directory(DIRECTORY_2); + create_test_directory(DIRECTORY_3); + create_test_directory(DIRECTORY_4); + CREATE(1); + CREATE(2); + CREATE(3); + CREATE(4); + CREATE(5); + CREATE(6); + CREATE(7); + CREATE(8); + CREATE(9); + CREATE(10); + CREATE(11); +} + +void create_test_directory(const char *directory) { + assert(mkdir(directory,0700)==0); +} + +void create_test_file(const char *filename, const char *contents) { + FILE *fp; + + printf("creating %s\n",filename); + fp = fopen(filename,"w+"); + assert(NULL!=fp); + + assert(strlen(contents)==fwrite(contents,sizeof(char),strlen(contents),fp)); + assert(fclose(fp)==0); +} diff --git a/rss/test/test_utils.h b/rss/test/test_utils.h new file mode 100644 index 0000000..5e3aff7 --- /dev/null +++ b/rss/test/test_utils.h @@ -0,0 +1,54 @@ +#ifndef __TEST_UTILS_H_ +#define __TEST_UTILS_H_ + +#include +#include +#include +#include +#include +#include + +#define DATADIR "/tmp/rss-create" + +#define DIRECTORY_1 DATADIR "/test" +#define DIRECTORY_2 DATADIR "/test2" +#define DIRECTORY_3 DATADIR "/test/test3" +#define DIRECTORY_4 DATADIR "/test/.dotfiles" + +#define FILE_1 DATADIR "/test/test.txt" +#define FILE_1_CONTENTS "test" +#define FILE_2 DATADIR "/test/test2.txt" +#define FILE_2_CONTENTS "test2" +#define FILE_3 DATADIR "/test/test3/hello.txt" +#define FILE_3_CONTENTS "test3" +#define FILE_4 DATADIR "/test/.test3.meta" +#define FILE_4_CONTENTS "description=nope\ndescription=this aly show\\na new line\npubDate=Sun, 16 May 2021 02:55:07 +0000\n" +#define FILE_5 DATADIR "/test/.test2.txt.meta" +#define FILE_5_CONTENTS "pubDate=Sat, 15 May 2021 11:01:04 +0000\nlink=0bc6c0de1ff1d26d84187a4d42fb9a2d0bc73a1d.torrent" +#define FILE_6 DATADIR "/test/.dotfiles/haha.txt" +#define FILE_6_CONTENTS "hahaha" + +#define FILE_7 DATADIR "/test2/.nope.txt.meta" +#define FILE_7_CONTENTS "guid=hello" +#define FILE_8 DATADIR "/test2/nope.txt" +#define FILE_8_CONTENTS "hahahasldkfjasldkfjaslkdfj" + +#define FILE_9 DATADIR "/.test.meta" +#define FILE_9_CONTENTS "description=this is a test description" +#define FILE_10 DATADIR "/.test2.meta" +#define FILE_10_CONTENTS "language=en-uk" + +#define FILE_11 DATADIR "/test/test4.txt" +#define FILE_11_CONTENTS "title=test\ntitle=test2\nlink=link1\nlink=link2\npubDate=date1\npubDate=date2\ndescription=desc1\ndescription=desc2\nguid=guid1\nguid=guid2\nlanguage=lan1\nlanguage=lan2" + +#define CREATE_FILE_NAME_LOADER(x) FILE_ ## x +#define CREATE_FILE_CONTENTS_LOADER(x) FILE_ ## x ## _CONTENTS +#define CREATE(x) create_test_file(CREATE_FILE_NAME_LOADER(x),CREATE_FILE_CONTENTS_LOADER(x)) + +void clean_env(); +void reset_env(); +void setup_env(); +void create_test_directory(const char*); +void create_test_file(const char*,const char*); + +#endif diff --git a/rss/test/util.now.tests.c b/rss/test/util.now.tests.c new file mode 100644 index 0000000..0ff4c9f --- /dev/null +++ b/rss/test/util.now.tests.c @@ -0,0 +1,20 @@ +#include "util.now.tests.h" + +int main() { + setup_env(); + + util_now_basic_tests(); + + clean_env(); + + return EXIT_SUCCESS; +} + +void util_now_basic_tests() { + char *p; + assert(now_string(NULL)==-1); + + assert(now_string(&p)==1); + + free(p); +} diff --git a/rss/test/util.now.tests.h b/rss/test/util.now.tests.h new file mode 100644 index 0000000..d9453b4 --- /dev/null +++ b/rss/test/util.now.tests.h @@ -0,0 +1,11 @@ +#ifndef __UTIL_NOW_TESTS_H_ +#define __UTIL_NOW_TESTS_H_ + +#include "test_utils.h" + +#include + +int main(); +void util_now_basic_tests(); + +#endif diff --git a/seederd/Makefile.am b/seederd/Makefile.am new file mode 100644 index 0000000..b5e4835 --- /dev/null +++ b/seederd/Makefile.am @@ -0,0 +1,21 @@ +AM_CXXFLAGS = \ + -DPREFIX=\"$(localstatedir)/seeder\" \ + -I$(builddir)/inc/ \ + -I$(srcdir)/inc/ + +bin_PROGRAMS = seederd + +seederd_SOURCES = \ + src/feed.cpp \ + src/main.cpp \ + src/seed.cpp \ + src/torrent.cpp + +seederd_SOURCES += \ + inc/feed.hpp \ + inc/main.h \ + inc/seed.hpp \ + inc/torrent.hpp + +seederd_LDFLAGS = -ltorrent-rasterbar -lboost_system -lpthread +seederd_LDADD = -lstdc++fs diff --git a/seederd/inc/consts.h b/seederd/inc/consts.h new file mode 100644 index 0000000..757788b --- /dev/null +++ b/seederd/inc/consts.h @@ -0,0 +1,7 @@ +#ifndef __CONSTS_H_ +#define __CONSTS_H_ + +#define DATADIR PREFIX "/data" +#define TORRENTDIR PREFIX "/torrents" + +#endif diff --git a/seederd/inc/feed.hpp b/seederd/inc/feed.hpp new file mode 100644 index 0000000..34cb8fa --- /dev/null +++ b/seederd/inc/feed.hpp @@ -0,0 +1,22 @@ +#ifndef __FEED_HPP_ +#define __FEED_HPP_ + +#include + +class feed { + public: + feed(lt::session& session, fs::path feed_path) : m_path(feed_path) { + for(auto &p: fs::directory_iterator(feed_path)) { + if(torrent::file_filter(p.path().string())) { + torrent t(session,p); + } + } + + torrent t(session,feed_path); + } + void save(); + private: + fs::path m_path; +}; + +#endif diff --git a/seederd/inc/main.h b/seederd/inc/main.h new file mode 100644 index 0000000..dcdcb49 --- /dev/null +++ b/seederd/inc/main.h @@ -0,0 +1,23 @@ +#ifndef __MAIN_H_ +#define __MAIN_H_ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +int main(int,char**); +void init(lt::session&); +void handle_alerts(lt::session&); +void remove_missing(lt::session&); +int setup_signal_handler(); +void signal_handler(int); + +#endif diff --git a/seederd/inc/seed.hpp b/seederd/inc/seed.hpp new file mode 100644 index 0000000..85b99dc --- /dev/null +++ b/seederd/inc/seed.hpp @@ -0,0 +1,25 @@ +#ifndef __SEED_HPP_ +#define __SEED_HPP_ + +#include + +#include +#include +#include + +namespace fs = std::filesystem; + +class seed { + public: + seed(lt::session& session, std::string torrent_filepath, std::string data_path) { + lt::add_torrent_params params; + params.save_path = data_path; + params.ti = boost::make_shared(torrent_filepath,0); + params.flags = lt::add_torrent_params::flag_seed_mode; + + session.async_add_torrent(params); + } + void static remove(); +}; + +#endif diff --git a/seederd/inc/torrent.hpp b/seederd/inc/torrent.hpp new file mode 100644 index 0000000..2b61b79 --- /dev/null +++ b/seederd/inc/torrent.hpp @@ -0,0 +1,37 @@ +#ifndef __TORRENT_HPP_ +#define __TORRENT_HPP_ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace fs = std::filesystem; + +class torrent { + public: + torrent(lt::session &session, fs::path p) : m_path(p) { + lt::add_files(m_fs,p,file_filter); + if(m_fs.num_files()>0) { + save(session); + } else { + std::cerr << "WARNING: no files found in " << p << "; skipping"<< std::endl; + } + }; + void add(fs::path); + void add_link_to_meta(std::string,std::string); + bool static file_filter(std::string const&); + void save(lt::session&); + private: + fs::path m_path; + lt::file_storage m_fs; +}; + +#endif diff --git a/seederd/src/feed.cpp b/seederd/src/feed.cpp new file mode 100644 index 0000000..562ae36 --- /dev/null +++ b/seederd/src/feed.cpp @@ -0,0 +1,7 @@ +#include + +namespace fs = std::filesystem; + +void feed::save() { + throw std::runtime_error("not implemented\n"); +} diff --git a/seederd/src/main.cpp b/seederd/src/main.cpp new file mode 100644 index 0000000..99103e1 --- /dev/null +++ b/seederd/src/main.cpp @@ -0,0 +1,124 @@ +#include + +namespace fs = std::filesystem; + +int setup_signal_handler() { + struct sigaction action; + + action.sa_handler = &signal_handler; + action.sa_flags = 0; + if(sigemptyset(&action.sa_mask)==-1) { + perror("sigemptyset"); + return -1; + } + + if(sigaddset(&action.sa_mask,SIGHUP)==-1) { + perror("sigaddset"); + return -1; + } + + if(sigaction(SIGHUP, &action, NULL)==-1) { + perror("sigaction"); + return -1; + } + + return 1; +} + +int main(int argc, char **argv) try { + if(setup_signal_handler()<0) { return EXIT_FAILURE; } + + std::cout << "creating required directories" << std::endl; + fs::create_directories(DATADIR); + std::cout << "created " << DATADIR << std::endl; + fs::create_directories(TORRENTDIR); + std::cout << "created " << TORRENTDIR << std::endl; + + lt::settings_pack settings; + lt::high_performance_seed(settings); + settings.set_int(lt::settings_pack::active_seeds,-1); + settings.set_int(lt::settings_pack::active_limit,-1); +// settings.set_int(lt::settings_pack::alert_mask,(lt::alert::session_log_notification + lt::alert::torrent_log_notification)); + lt::session session(settings); + + init(session); + + handle_alerts(session); + + return EXIT_SUCCESS; +} +catch(std::exception const& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + return EXIT_FAILURE; +} + +void init(lt::session& session) { + for(auto &p: fs::directory_iterator(DATADIR)) { + if(p.is_directory()) { + if(!torrent::file_filter(p.path().string())) { continue; } + + std::cout << "creating torrents in " << p.path() << "..." << std::endl; + + feed fd(session,p.path()); + + std::cout<< "done!" << std::endl; + } + } + + remove_missing(session); + + system("update-feeds"); +} + +void remove_missing(lt::session& session) { + // add all .torrent files + std::unordered_map found; + for(auto &p: fs::directory_iterator(TORRENTDIR)) { + if(p.is_regular_file()) { + fs::path path = p.path(); + found.insert({path.stem().string(),path}); + } + } + + std::vector current = session.get_torrents(); + for(auto t: current) { + std::stringstream hash; + hash << t.info_hash(); + + // remove torrent if .torrent file no longer exists + auto search = found.find(hash.str()); + if(search==found.end()) { + session.remove_torrent(t); + } + } +} + + +bool reload = false; + +void handle_alerts(lt::session& session) { + for (;;) { + if(reload) { + reload = false; + std::cout << "reloading configuration..." << std::endl; + init(session); + std::cout << "configuration reloaded" << std::endl; + } + + lt::alert const* a = session.wait_for_alert(lt::seconds(10)); + if(nullptr==a) { continue; } + + std::vector alerts; + session.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) { + std::cout << "[" << a->type() << "]: " << a->message() << std::endl; + if (lt::alert_cast(a)) { break; } + } + } + std::cout << "error, shutting down..." << std::endl; +} + +void signal_handler(int signo) { + reload = true; +} diff --git a/seederd/src/seed.cpp b/seederd/src/seed.cpp new file mode 100644 index 0000000..f644630 --- /dev/null +++ b/seederd/src/seed.cpp @@ -0,0 +1,5 @@ +#include + +void seed::remove() { + std::cerr << "not implemented\n"; +} diff --git a/seederd/src/torrent.cpp b/seederd/src/torrent.cpp new file mode 100644 index 0000000..e106315 --- /dev/null +++ b/seederd/src/torrent.cpp @@ -0,0 +1,92 @@ +#include + +void torrent::add(fs::path p) { + lt::add_files(m_fs,p,file_filter); +} + +void torrent::add_link_to_meta(std::string link, std::string magnet_uri) { + std::fstream meta_file; + + fs::path meta_filepath = m_path.parent_path(); + meta_filepath /= "." + m_path.filename().string() + ".meta"; + + bool hasLink = false; + bool hasGuid = false; + + meta_file.open(meta_filepath.string(), std::ios_base::in); + if(meta_file.is_open()) { + std::string line, searchLink, searchGuid; + searchLink = "link=" + link; + searchGuid = "guid=" + magnet_uri; + while(getline(meta_file,line)) { + if(line==searchLink) { hasLink = true; } + if(line==searchGuid) { hasGuid = true; } + } + + meta_file.close(); + } + + meta_file.open(meta_filepath.string(), std::ios_base::out | std::ios_base::app); + if(!meta_file.is_open()) { + std::cerr << "something went wrong" << std::endl; + } + + if(!hasLink) { + meta_file << "link=" << link << "\n"; + } + + if(!hasGuid) { + meta_file << "guid=" << magnet_uri << "\n"; + } + + meta_file.close(); +} + +bool torrent::file_filter(std::string const& f) { + if(f.empty()) { return false; } + + char const *first = f.c_str(); + char const *sep = strrchr(first, '/'); + + if(nullptr==sep) { + sep = first; + } else { + sep++; + } + + if(sep[0]=='.') { + std::cerr << "skipped: " << f << std::endl; + return false; + } + + std::cerr << "added: " << f << std::endl; + return true; +} + +void torrent::save(lt::session &session) { + fs::path temp_path = PREFIX; + temp_path /= ".tmp.torrent"; + std::cerr << "temp path: " << temp_path << std::endl; + + fs::path parent_path = m_path.parent_path(); + lt::create_torrent t(m_fs); + lt::set_piece_hashes(t, parent_path); + + std::ofstream out(temp_path, std::ios_base::binary); + lt::bencode(std::ostream_iterator(out), t.generate()); + out.close(); + + lt::torrent_info info(temp_path); + + std::stringstream filename; + filename << info.info_hash() << ".torrent"; + fs::path out_path(TORRENTDIR); + out_path /= filename.str(); + + add_link_to_meta(filename.str(),lt::make_magnet_uri(info)); + + if(!fs::exists(out_path)) { rename(temp_path,out_path); } + remove(temp_path); + + seed sd(session,out_path.string(),parent_path.string()); +} -- 2.30.2