# HG changeset patch # User František Kučera # Date 1609771512 -3600 # Node ID 15d87fdd6e6cf2cebcaf6f1ca893109e4c7dea1f # Parent 5b351628a37767e22605f62e454fffcb78d5b3dc use Logger instead of messing with STDIO directly diff -r 5b351628a377 -r 15d87fdd6e6c AlsaBridge.cpp --- a/AlsaBridge.cpp Mon Jan 04 13:38:08 2021 +0100 +++ b/AlsaBridge.cpp Mon Jan 04 15:45:12 2021 +0100 @@ -15,6 +15,7 @@ * along with this program. If not, see . */ #include +#include #include #include #include @@ -24,13 +25,17 @@ #include #include "AlsaBridge.h" +#include "Logger.h" namespace djmfix { namespace alsa { +using L = djmfix::logging::Level; + class AlsaBridgeImpl : public AlsaBridge, private djmfix::MidiSender { private: djmfix::DJMFix* djmFix; + djmfix::logging::Logger* logger; snd_rawmidi_t* input; snd_rawmidi_t* output; std::thread receivingThread; @@ -41,22 +46,27 @@ std::vector cardNumbers; - std::cerr << "Looking for available cards:" << std::endl; // TODO: do not mess STDIO + logger->log(L::INFO, "Looking for available cards:"); for (int card = -1; snd_card_next(&card) == 0 && card >= 0;) { char* longName = nullptr; snd_card_get_longname(card, &longName); - std::cerr << "card: #" << card << ": '" << longName << "'"; // TODO: do not mess STDIO + + std::stringstream logMessage; + logMessage << " - card: #" << card << ": '" << longName << "'"; + if (std::regex_match(longName, cardNamePattern)) { cardNumbers.push_back(card); - std::cerr << " [matches]"; // TODO: do not mess STDIO + logMessage << " [matches]"; } - std::cerr << std::endl; + + logger->log(L::INFO, logMessage.str()); + free(longName); } if (cardNumbers.size() == 1) { - std::cerr << "Going to fix card #" << cardNumbers[0] << std::endl; // TODO: do not mess STDIO + logger->log(L::INFO, "Going to fix card #" + std::to_string(cardNumbers[0])); return "hw:" + std::to_string(cardNumbers[0]); } else if (cardNumbers.empty()) { throw std::invalid_argument("No card with matching name found. Is the card connected? Maybe try to provide different name pattern."); @@ -82,7 +92,7 @@ } public: - AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& cardNamePattern) : djmFix(djmFix) { + AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& cardNamePattern, djmfix::logging::Logger* logger) : djmFix(djmFix), logger(logger ? logger : djmfix::logging::blackhole()) { if (djmFix == nullptr) throw std::invalid_argument("need a djmFix for AlsaBridge"); std::string deviceName = findDeviceName(std::regex(cardNamePattern)); @@ -95,10 +105,10 @@ } virtual ~AlsaBridgeImpl() { - // TODO: do not use raw/exclusive access to the device + // TODO: do not use raw/exclusive access to the MIDI device snd_rawmidi_close(input); snd_rawmidi_close(output); - std::cerr << "~AlsaBridgeImpl()" << std::endl; // TODO: do not mess STDIO + logger->log(L::FINE, "~AlsaBridgeImpl()"); } virtual void start() override { @@ -115,13 +125,13 @@ virtual void send(MidiMessage midiMessage) override { std::lock_guard lock(midiMutex); ssize_t length = snd_rawmidi_write(output, midiMessage.data(), midiMessage.size()); - std::cerr << "AlsaBridgeImpl::send(): length = " << length << std::endl; // TODO: do not mess STDIO + logger->log(L::INFO, "AlsaBridgeImpl::send(): length = " + std::to_string(length)); } }; -AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName) { - return new AlsaBridgeImpl(djmFix, deviceName); +AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName, djmfix::logging::Logger* logger) { + return new AlsaBridgeImpl(djmFix, deviceName, logger); } } diff -r 5b351628a377 -r 15d87fdd6e6c AlsaBridge.h --- a/AlsaBridge.h Mon Jan 04 13:38:08 2021 +0100 +++ b/AlsaBridge.h Mon Jan 04 15:45:12 2021 +0100 @@ -19,6 +19,7 @@ #include #include "DJMFix.h" +#include "Logger.h" namespace djmfix { namespace alsa { @@ -31,7 +32,7 @@ }; -AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& cardNamePattern); +AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& cardNamePattern, djmfix::logging::Logger* logger); } } diff -r 5b351628a377 -r 15d87fdd6e6c DJMFix.cpp --- a/DJMFix.cpp Mon Jan 04 13:38:08 2021 +0100 +++ b/DJMFix.cpp Mon Jan 04 15:45:12 2021 +0100 @@ -15,6 +15,7 @@ * along with this program. If not, see . */ #include +#include #include #include #include @@ -27,11 +28,13 @@ namespace djmfix { +using L = djmfix::logging::Level; using Bytes = std::vector; class DJMFixImpl : public DJMFix { private: MidiSender* midiSender; + djmfix::logging::Logger* logger; std::thread keepAliveThread; std::recursive_mutex midiMutex; std::atomic running{false}; @@ -41,7 +44,7 @@ void run() { while (!stopped) { - std::cerr << "DJMFixImpl::run()" << std::endl; // TODO: do not mess STDIO + logger->log(L::FINE, "DJMFixImpl::run()"); if (sendKeepAlive) send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7}); std::this_thread::sleep_for(std::chrono::milliseconds(200)); } @@ -117,18 +120,21 @@ public: + DJMFixImpl(djmfix::logging::Logger* logger) : logger(logger ? logger : djmfix::logging::blackhole()) { + } + virtual ~DJMFixImpl() override { - std::cerr << "~DJMFixImpl()" << std::endl; // TODO: do not mess STDIO + logger->log(L::FINE, "~DJMFixImpl()"); if (running) stop(); } void setMidiSender(MidiSender* midiSender) { - std::cerr << "DJMFixImpl::setMidiSender()" << std::endl; // TODO: do not mess STDIO + logger->log(L::FINE, "DJMFixImpl::setMidiSender()"); this->midiSender = midiSender; } virtual void receive(const MidiMessage& midiMessage) override { - std::cerr << "DJMFixImpl::receive(): size = " << midiMessage.size() << " data = " << toString(midiMessage) << std::endl; // TODO: do not mess STDIO + logger->log(L::INFO, "received message: size = " + std::to_string(midiMessage.size()) + " data = " + toString(midiMessage)); std::lock_guard lock(midiMutex); @@ -139,7 +145,7 @@ seed2 = Bytes(midiMessage.begin() + 45, midiMessage.begin() + 45 + 8); hash1 = normalize(hash1); seed2 = normalize(seed2); - std::cerr << "DJMFixImpl::receive(): got message with hash1 = " << toString(hash1) << " and seed2 = " << toString(seed2) << std::endl; // TODO: do not mess STDIO + logger->log(L::INFO, "got message with hash1 = " + toString(hash1) + " and seed2 = " + toString(seed2)); Bytes seed0 = {0x68, 0x01, 0x31, 0xFB}; Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00}; @@ -147,19 +153,20 @@ Bytes hash1check = toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2)))); if (equals(hash1, hash1check)) { - std::cerr << "DJMFixImpl::receive(): hash1 verification: OK" << std::endl; // TODO: do not mess STDIO + logger->log(L::INFO, "hash1 verification: OK"); Bytes hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2)))); send(concat({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x14, 0x38, 0x01, 0x0b, 0x50, 0x69, 0x6f, 0x6e, 0x65, 0x65, 0x72, 0x44, 0x4a, 0x02, 0x0b, 0x72, 0x65, 0x6b, 0x6f, 0x72, 0x64, 0x62, 0x6f, 0x78, 0x04, 0x0a}, concat(denormalize(hash2),{0x05, 0x16, 0x05, 0x09, 0x0b, 0x05, 0x04, 0x0b, 0x0f, 0x0e, 0x0e, 0x04, 0x04, 0x0a, 0x05, 0x0a, 0x0c, 0x08, 0x0e, 0x04, 0x0c, 0x05, 0xf7}))); } else { - std::cerr - << "DJMFixImpl::receive(): hash1 verification failed: " + std::stringstream logMessage; + logMessage + << "hash1 verification failed: " << " midiMessage = " << toString(midiMessage) << " seed0 = " << toString(seed0) << " seed1 = " << toString(seed1) << " seed2 = " << toString(seed2) << " hash1 = " << toString(hash1) - << " hash1check = " << toString(hash1check) - << std::endl; + << " hash1check = " << toString(hash1check); + logger->log(L::SEVERE, logMessage.str()); // TODO: graceful death } } else if (midiMessage.size() == 12 && midiMessage[9] == 0x15) { @@ -169,7 +176,7 @@ } void start() override { - std::cerr << "DJMFixImpl::start()" << std::endl; // TODO: do not mess STDIO + logger->log(L::FINE, "DJMFixImpl::start()"); if (midiSender == nullptr) throw std::logic_error("need a midiSender when starting DJMFix"); // TODO: methods for parsing and constructing messages from parts (TLV) @@ -184,12 +191,12 @@ stopped = true; keepAliveThread.join(); running = false; - std::cerr << "DJMFixImpl::stop()" << std::endl; // TODO: do not mess STDIO + logger->log(L::FINE, "DJMFixImpl::stop()"); } }; -DJMFix* create() { - return new DJMFixImpl(); +DJMFix* create(djmfix::logging::Logger* logger) { + return new DJMFixImpl(logger); } } diff -r 5b351628a377 -r 15d87fdd6e6c DJMFix.h --- a/DJMFix.h Mon Jan 04 13:38:08 2021 +0100 +++ b/DJMFix.h Mon Jan 04 15:45:12 2021 +0100 @@ -18,6 +18,8 @@ #include +#include "Logger.h" + namespace djmfix { using MidiMessage = std::vector; @@ -37,6 +39,6 @@ virtual void stop() = 0; }; -DJMFix* create(); +DJMFix* create(djmfix::logging::Logger* logger); } diff -r 5b351628a377 -r 15d87fdd6e6c Logger.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Logger.cpp Mon Jan 04 15:45:12 2021 +0100 @@ -0,0 +1,78 @@ +/** + * DJM-Fix + * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * + * 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, version 3 of the License. + * + * 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 . + */ +#include +#include +#include + +#include "Logger.h" + +namespace djmfix { +namespace logging { + +class Blackhole : public Logger { +public: + + virtual void log(Level level, const std::string& message) override { + } +}; + +class LoggerImpl : public Logger { +private: + std::ostream& output; + Level minLevel; + + std::string getTimestamp() { + auto now = std::chrono::system_clock::now(); + auto itt = std::chrono::system_clock::to_time_t(now); + std::ostringstream ss; + ss << std::put_time(localtime(&itt), "%FT%T%z"); + return ss.str(); + } + + std::string toString(Level level) { + if (level == Level::SEVERE) return "SEVERE"; + else if (level == Level::WARNING) return "WARNING"; + else if (level == Level::INFO) return "INFO"; + else if (level == Level::CONFIG) return "CONFIG"; + else if (level == Level::FINE) return "FINE"; + else if (level == Level::FINER) return "FINER"; + else if (level == Level::FINEST) return "FINEST"; + else return "UNKNOWN"; + } + +public: + + LoggerImpl(std::ostream& output, Level minLevel) : output(output), minLevel(minLevel) { + } + + virtual void log(Level level, const std::string& message) override { + if (level <= minLevel) { + output << getTimestamp() << " " << std::setw(8) << toString(level) << ": " << message << std::endl; + } + } +}; + +Logger* create(std::ostream& output, Level minLevel) { + return new LoggerImpl(output, minLevel); +} + +Logger* blackhole() { + return new Blackhole(); +} + +} +} diff -r 5b351628a377 -r 15d87fdd6e6c Logger.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Logger.h Mon Jan 04 15:45:12 2021 +0100 @@ -0,0 +1,44 @@ +/** + * DJM-Fix + * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info) + * + * 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, version 3 of the License. + * + * 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 . + */ +#pragma once + +#include + +namespace djmfix { +namespace logging { + +enum class Level { + SEVERE, + WARNING, + INFO, + CONFIG, + FINE, + FINER, + FINEST, +}; + +class Logger { +public: + virtual ~Logger() = default; + virtual void log(Level level, const std::string& message) = 0; +}; + +Logger* create(std::ostream& output, Level minLevel); +Logger* blackhole(); + +} +} \ No newline at end of file diff -r 5b351628a377 -r 15d87fdd6e6c Makefile --- a/Makefile Mon Jan 04 13:38:08 2021 +0100 +++ b/Makefile Mon Jan 04 15:45:12 2021 +0100 @@ -25,6 +25,6 @@ .PHONY: all clean run -build/djm-fix: DJMFix.cpp DJMFix.h AlsaBridge.cpp AlsaBridge.h djm-fix.cpp +build/djm-fix: DJMFix.cpp DJMFix.h AlsaBridge.cpp AlsaBridge.h Logger.cpp Logger.h djm-fix.cpp mkdir -p build - g++ -o $@ DJMFix.cpp AlsaBridge.cpp djm-fix.cpp $(CXXFLAGS) + g++ -o $@ DJMFix.cpp AlsaBridge.cpp Logger.cpp djm-fix.cpp $(CXXFLAGS) diff -r 5b351628a377 -r 15d87fdd6e6c djm-fix.cpp --- a/djm-fix.cpp Mon Jan 04 13:38:08 2021 +0100 +++ b/djm-fix.cpp Mon Jan 04 15:45:12 2021 +0100 @@ -24,12 +24,12 @@ #include "DJMFix.h" #include "AlsaBridge.h" +#include "Logger.h" static std::atomic run{true}; void interrupt(int signal) { run = false; - std::cerr << "interrupt()" << std::endl; // TODO: do not mess STDIO } /** @@ -80,19 +80,26 @@ */ int main(int argc, char**argv) { + using L = djmfix::logging::Level; + std::unique_ptr logger(djmfix::logging::create(std::cerr, L::INFO)); try { + logger->log(L::INFO, "djm-fix started"); std::string cardNamePattern = argc == 2 ? argv[1] : "Pioneer DJ.*"; signal(SIGINT, interrupt); - std::unique_ptr djmFix(djmfix::create()); - std::unique_ptr alsaBridge(djmfix::alsa::create(djmFix.get(), cardNamePattern)); + std::unique_ptr djmFix(djmfix::create(logger.get())); + std::unique_ptr alsaBridge(djmfix::alsa::create(djmFix.get(), cardNamePattern, logger.get())); alsaBridge->start(); while (run) std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + std::cerr << std::endl; + logger->log(L::INFO, "djm-fix stopping"); + alsaBridge->stop(); return 0; } catch (const std::exception& e) { - std::cerr << "ERROR: " << e.what() << std::endl; // TODO: do not mess STDIO + logger->log(L::SEVERE, e.what()); } }