use Logger instead of messing with STDIO directly v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Mon, 04 Jan 2021 15:45:12 +0100
branchv_0
changeset 1215d87fdd6e6c
parent 11 5b351628a377
child 13 334b727f7516
use Logger instead of messing with STDIO directly
AlsaBridge.cpp
AlsaBridge.h
DJMFix.cpp
DJMFix.h
Logger.cpp
Logger.h
Makefile
djm-fix.cpp
     1.1 --- a/AlsaBridge.cpp	Mon Jan 04 13:38:08 2021 +0100
     1.2 +++ b/AlsaBridge.cpp	Mon Jan 04 15:45:12 2021 +0100
     1.3 @@ -15,6 +15,7 @@
     1.4   * along with this program. If not, see <http://www.gnu.org/licenses/>.
     1.5   */
     1.6  #include <iostream>
     1.7 +#include <sstream>
     1.8  #include <stdexcept>
     1.9  #include <thread>
    1.10  #include <mutex>
    1.11 @@ -24,13 +25,17 @@
    1.12  #include <alsa/asoundlib.h>
    1.13  
    1.14  #include "AlsaBridge.h"
    1.15 +#include "Logger.h"
    1.16  
    1.17  namespace djmfix {
    1.18  namespace alsa {
    1.19  
    1.20 +using L = djmfix::logging::Level;
    1.21 +
    1.22  class AlsaBridgeImpl : public AlsaBridge, private djmfix::MidiSender {
    1.23  private:
    1.24  	djmfix::DJMFix* djmFix;
    1.25 +	djmfix::logging::Logger* logger;
    1.26  	snd_rawmidi_t* input;
    1.27  	snd_rawmidi_t* output;
    1.28  	std::thread receivingThread;
    1.29 @@ -41,22 +46,27 @@
    1.30  
    1.31  		std::vector<int> cardNumbers;
    1.32  
    1.33 -		std::cerr << "Looking for available cards:" << std::endl; // TODO: do not mess STDIO
    1.34 +		logger->log(L::INFO, "Looking for available cards:");
    1.35  
    1.36  		for (int card = -1; snd_card_next(&card) == 0 && card >= 0;) {
    1.37  			char* longName = nullptr;
    1.38  			snd_card_get_longname(card, &longName);
    1.39 -			std::cerr << "card: #" << card << ": '" << longName << "'"; // TODO: do not mess STDIO
    1.40 +
    1.41 +			std::stringstream logMessage;
    1.42 +			logMessage << " - card: #" << card << ": '" << longName << "'";
    1.43 +
    1.44  			if (std::regex_match(longName, cardNamePattern)) {
    1.45  				cardNumbers.push_back(card);
    1.46 -				std::cerr << " [matches]"; // TODO: do not mess STDIO
    1.47 +				logMessage << " [matches]";
    1.48  			}
    1.49 -			std::cerr << std::endl;
    1.50 +
    1.51 +			logger->log(L::INFO, logMessage.str());
    1.52 +
    1.53  			free(longName);
    1.54  		}
    1.55  
    1.56  		if (cardNumbers.size() == 1) {
    1.57 -			std::cerr << "Going to fix card #" << cardNumbers[0] << std::endl; // TODO: do not mess STDIO
    1.58 +			logger->log(L::INFO, "Going to fix card #" + std::to_string(cardNumbers[0]));
    1.59  			return "hw:" + std::to_string(cardNumbers[0]);
    1.60  		} else if (cardNumbers.empty()) {
    1.61  			throw std::invalid_argument("No card with matching name found. Is the card connected? Maybe try to provide different name pattern.");
    1.62 @@ -82,7 +92,7 @@
    1.63  	}
    1.64  public:
    1.65  
    1.66 -	AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& cardNamePattern) : djmFix(djmFix) {
    1.67 +	AlsaBridgeImpl(djmfix::DJMFix* djmFix, const std::string& cardNamePattern, djmfix::logging::Logger* logger) : djmFix(djmFix), logger(logger ? logger : djmfix::logging::blackhole()) {
    1.68  		if (djmFix == nullptr) throw std::invalid_argument("need a djmFix for AlsaBridge");
    1.69  
    1.70  		std::string deviceName = findDeviceName(std::regex(cardNamePattern));
    1.71 @@ -95,10 +105,10 @@
    1.72  	}
    1.73  
    1.74  	virtual ~AlsaBridgeImpl() {
    1.75 -		// TODO: do not use raw/exclusive access to the device
    1.76 +		// TODO: do not use raw/exclusive access to the MIDI device
    1.77  		snd_rawmidi_close(input);
    1.78  		snd_rawmidi_close(output);
    1.79 -		std::cerr << "~AlsaBridgeImpl()" << std::endl; // TODO: do not mess STDIO
    1.80 +		logger->log(L::FINE, "~AlsaBridgeImpl()");
    1.81  	}
    1.82  
    1.83  	virtual void start() override {
    1.84 @@ -115,13 +125,13 @@
    1.85  	virtual void send(MidiMessage midiMessage) override {
    1.86  		std::lock_guard<std::recursive_mutex> lock(midiMutex);
    1.87  		ssize_t length = snd_rawmidi_write(output, midiMessage.data(), midiMessage.size());
    1.88 -		std::cerr << "AlsaBridgeImpl::send(): length = " << length << std::endl; // TODO: do not mess STDIO
    1.89 +		logger->log(L::INFO, "AlsaBridgeImpl::send(): length = " + std::to_string(length));
    1.90  	}
    1.91  
    1.92  };
    1.93  
    1.94 -AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName) {
    1.95 -	return new AlsaBridgeImpl(djmFix, deviceName);
    1.96 +AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& deviceName, djmfix::logging::Logger* logger) {
    1.97 +	return new AlsaBridgeImpl(djmFix, deviceName, logger);
    1.98  }
    1.99  
   1.100  }
     2.1 --- a/AlsaBridge.h	Mon Jan 04 13:38:08 2021 +0100
     2.2 +++ b/AlsaBridge.h	Mon Jan 04 15:45:12 2021 +0100
     2.3 @@ -19,6 +19,7 @@
     2.4  #include <string>
     2.5  
     2.6  #include "DJMFix.h"
     2.7 +#include "Logger.h"
     2.8  
     2.9  namespace djmfix {
    2.10  namespace alsa {
    2.11 @@ -31,7 +32,7 @@
    2.12  
    2.13  };
    2.14  
    2.15 -AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& cardNamePattern);
    2.16 +AlsaBridge* create(djmfix::DJMFix* djmFix, const std::string& cardNamePattern, djmfix::logging::Logger* logger);
    2.17  
    2.18  }
    2.19  }
     3.1 --- a/DJMFix.cpp	Mon Jan 04 13:38:08 2021 +0100
     3.2 +++ b/DJMFix.cpp	Mon Jan 04 15:45:12 2021 +0100
     3.3 @@ -15,6 +15,7 @@
     3.4   * along with this program. If not, see <http://www.gnu.org/licenses/>.
     3.5   */
     3.6  #include <iostream>
     3.7 +#include <sstream>
     3.8  #include <iomanip>
     3.9  #include <thread>
    3.10  #include <mutex>
    3.11 @@ -27,11 +28,13 @@
    3.12  
    3.13  namespace djmfix {
    3.14  
    3.15 +using L = djmfix::logging::Level;
    3.16  using Bytes = std::vector<uint8_t>;
    3.17  
    3.18  class DJMFixImpl : public DJMFix {
    3.19  private:
    3.20  	MidiSender* midiSender;
    3.21 +	djmfix::logging::Logger* logger;
    3.22  	std::thread keepAliveThread;
    3.23  	std::recursive_mutex midiMutex;
    3.24  	std::atomic<bool> running{false};
    3.25 @@ -41,7 +44,7 @@
    3.26  
    3.27  	void run() {
    3.28  		while (!stopped) {
    3.29 -			std::cerr << "DJMFixImpl::run()" << std::endl; // TODO: do not mess STDIO
    3.30 +			logger->log(L::FINE, "DJMFixImpl::run()");
    3.31  			if (sendKeepAlive) send({0xf0, 0x00, 0x40, 0x05, 0x00, 0x00, 0x00, 0x17, 0x00, 0x50, 0x01, 0xf7});
    3.32  			std::this_thread::sleep_for(std::chrono::milliseconds(200));
    3.33  		}
    3.34 @@ -117,18 +120,21 @@
    3.35  
    3.36  public:
    3.37  
    3.38 +	DJMFixImpl(djmfix::logging::Logger* logger) : logger(logger ? logger : djmfix::logging::blackhole()) {
    3.39 +	}
    3.40 +
    3.41  	virtual ~DJMFixImpl() override {
    3.42 -		std::cerr << "~DJMFixImpl()" << std::endl; // TODO: do not mess STDIO
    3.43 +		logger->log(L::FINE, "~DJMFixImpl()");
    3.44  		if (running) stop();
    3.45  	}
    3.46  
    3.47  	void setMidiSender(MidiSender* midiSender) {
    3.48 -		std::cerr << "DJMFixImpl::setMidiSender()" << std::endl; // TODO: do not mess STDIO
    3.49 +		logger->log(L::FINE, "DJMFixImpl::setMidiSender()");
    3.50  		this->midiSender = midiSender;
    3.51  	}
    3.52  
    3.53  	virtual void receive(const MidiMessage& midiMessage) override {
    3.54 -		std::cerr << "DJMFixImpl::receive(): size = " << midiMessage.size() << " data = " << toString(midiMessage) << std::endl; // TODO: do not mess STDIO
    3.55 +		logger->log(L::INFO, "received message: size = " + std::to_string(midiMessage.size()) + " data = " + toString(midiMessage));
    3.56  		std::lock_guard<std::recursive_mutex> lock(midiMutex);
    3.57  
    3.58  
    3.59 @@ -139,7 +145,7 @@
    3.60  			seed2 = Bytes(midiMessage.begin() + 45, midiMessage.begin() + 45 + 8);
    3.61  			hash1 = normalize(hash1);
    3.62  			seed2 = normalize(seed2);
    3.63 -			std::cerr << "DJMFixImpl::receive(): got message with hash1 = " << toString(hash1) << " and seed2 = " << toString(seed2) << std::endl; // TODO: do not mess STDIO
    3.64 +			logger->log(L::INFO, "got message with hash1 = " + toString(hash1) + " and seed2 = " + toString(seed2));
    3.65  
    3.66  			Bytes seed0 = {0x68, 0x01, 0x31, 0xFB};
    3.67  			Bytes seed1 = {0x29, 0x00, 0x00, 0x00, 0x23, 0x48, 0x00, 0x00};
    3.68 @@ -147,19 +153,20 @@
    3.69  			Bytes hash1check = toBytes(fnv32hash(concat(seed1, xOR(seed0, seed2))));
    3.70  
    3.71  			if (equals(hash1, hash1check)) {
    3.72 -				std::cerr << "DJMFixImpl::receive(): hash1 verification: OK" << std::endl; // TODO: do not mess STDIO
    3.73 +				logger->log(L::INFO, "hash1 verification: OK");
    3.74  				Bytes hash2 = toBytes(fnv32hash(concat(seed2, xOR(seed0, seed2))));
    3.75  				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})));
    3.76  			} else {
    3.77 -				std::cerr
    3.78 -						<< "DJMFixImpl::receive(): hash1 verification failed: "
    3.79 +				std::stringstream logMessage;
    3.80 +				logMessage
    3.81 +						<< "hash1 verification failed: "
    3.82  						<< " midiMessage = " << toString(midiMessage)
    3.83  						<< " seed0 = " << toString(seed0)
    3.84  						<< " seed1 = " << toString(seed1)
    3.85  						<< " seed2 = " << toString(seed2)
    3.86  						<< " hash1 = " << toString(hash1)
    3.87 -						<< " hash1check = " << toString(hash1check)
    3.88 -						<< std::endl;
    3.89 +						<< " hash1check = " << toString(hash1check);
    3.90 +				logger->log(L::SEVERE, logMessage.str());
    3.91  				// TODO: graceful death
    3.92  			}
    3.93  		} else if (midiMessage.size() == 12 && midiMessage[9] == 0x15) {
    3.94 @@ -169,7 +176,7 @@
    3.95  	}
    3.96  
    3.97  	void start() override {
    3.98 -		std::cerr << "DJMFixImpl::start()" << std::endl; // TODO: do not mess STDIO
    3.99 +		logger->log(L::FINE, "DJMFixImpl::start()");
   3.100  		if (midiSender == nullptr) throw std::logic_error("need a midiSender when starting DJMFix");
   3.101  
   3.102  		// TODO: methods for parsing and constructing messages from parts (TLV)
   3.103 @@ -184,12 +191,12 @@
   3.104  		stopped = true;
   3.105  		keepAliveThread.join();
   3.106  		running = false;
   3.107 -		std::cerr << "DJMFixImpl::stop()" << std::endl; // TODO: do not mess STDIO
   3.108 +		logger->log(L::FINE, "DJMFixImpl::stop()");
   3.109  	}
   3.110  };
   3.111  
   3.112 -DJMFix* create() {
   3.113 -	return new DJMFixImpl();
   3.114 +DJMFix* create(djmfix::logging::Logger* logger) {
   3.115 +	return new DJMFixImpl(logger);
   3.116  }
   3.117  
   3.118  }
     4.1 --- a/DJMFix.h	Mon Jan 04 13:38:08 2021 +0100
     4.2 +++ b/DJMFix.h	Mon Jan 04 15:45:12 2021 +0100
     4.3 @@ -18,6 +18,8 @@
     4.4  
     4.5  #include <vector>
     4.6  
     4.7 +#include "Logger.h"
     4.8 +
     4.9  namespace djmfix {
    4.10  
    4.11  using MidiMessage = std::vector<uint8_t>;
    4.12 @@ -37,6 +39,6 @@
    4.13  	virtual void stop() = 0;
    4.14  };
    4.15  
    4.16 -DJMFix* create();
    4.17 +DJMFix* create(djmfix::logging::Logger* logger);
    4.18  
    4.19  }
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/Logger.cpp	Mon Jan 04 15:45:12 2021 +0100
     5.3 @@ -0,0 +1,78 @@
     5.4 +/**
     5.5 + * DJM-Fix
     5.6 + * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
     5.7 + *
     5.8 + * This program is free software: you can redistribute it and/or modify
     5.9 + * it under the terms of the GNU General Public License as published by
    5.10 + * the Free Software Foundation, version 3 of the License.
    5.11 + *
    5.12 + * This program is distributed in the hope that it will be useful,
    5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    5.15 + * GNU General Public License for more details.
    5.16 + *
    5.17 + * You should have received a copy of the GNU General Public License
    5.18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
    5.19 + */
    5.20 +#include <chrono>
    5.21 +#include <iomanip>
    5.22 +#include <sstream>
    5.23 +
    5.24 +#include "Logger.h"
    5.25 +
    5.26 +namespace djmfix {
    5.27 +namespace logging {
    5.28 +
    5.29 +class Blackhole : public Logger {
    5.30 +public:
    5.31 +
    5.32 +	virtual void log(Level level, const std::string& message) override {
    5.33 +	}
    5.34 +};
    5.35 +
    5.36 +class LoggerImpl : public Logger {
    5.37 +private:
    5.38 +	std::ostream& output;
    5.39 +	Level minLevel;
    5.40 +
    5.41 +	std::string getTimestamp() {
    5.42 +		auto now = std::chrono::system_clock::now();
    5.43 +		auto itt = std::chrono::system_clock::to_time_t(now);
    5.44 +		std::ostringstream ss;
    5.45 +		ss << std::put_time(localtime(&itt), "%FT%T%z");
    5.46 +		return ss.str();
    5.47 +	}
    5.48 +
    5.49 +	std::string toString(Level level) {
    5.50 +		if (level == Level::SEVERE) return "SEVERE";
    5.51 +		else if (level == Level::WARNING) return "WARNING";
    5.52 +		else if (level == Level::INFO) return "INFO";
    5.53 +		else if (level == Level::CONFIG) return "CONFIG";
    5.54 +		else if (level == Level::FINE) return "FINE";
    5.55 +		else if (level == Level::FINER) return "FINER";
    5.56 +		else if (level == Level::FINEST) return "FINEST";
    5.57 +		else return "UNKNOWN";
    5.58 +	}
    5.59 +
    5.60 +public:
    5.61 +
    5.62 +	LoggerImpl(std::ostream& output, Level minLevel) : output(output), minLevel(minLevel) {
    5.63 +	}
    5.64 +
    5.65 +	virtual void log(Level level, const std::string& message) override {
    5.66 +		if (level <= minLevel) {
    5.67 +			output << getTimestamp() << " " << std::setw(8) << toString(level) << ":  " << message << std::endl;
    5.68 +		}
    5.69 +	}
    5.70 +};
    5.71 +
    5.72 +Logger* create(std::ostream& output, Level minLevel) {
    5.73 +	return new LoggerImpl(output, minLevel);
    5.74 +}
    5.75 +
    5.76 +Logger* blackhole() {
    5.77 +	return new Blackhole();
    5.78 +}
    5.79 +
    5.80 +}
    5.81 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/Logger.h	Mon Jan 04 15:45:12 2021 +0100
     6.3 @@ -0,0 +1,44 @@
     6.4 +/**
     6.5 + * DJM-Fix
     6.6 + * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
     6.7 + *
     6.8 + * This program is free software: you can redistribute it and/or modify
     6.9 + * it under the terms of the GNU General Public License as published by
    6.10 + * the Free Software Foundation, version 3 of the License.
    6.11 + *
    6.12 + * This program is distributed in the hope that it will be useful,
    6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    6.15 + * GNU General Public License for more details.
    6.16 + *
    6.17 + * You should have received a copy of the GNU General Public License
    6.18 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
    6.19 + */
    6.20 +#pragma once
    6.21 +
    6.22 +#include <iostream>
    6.23 +
    6.24 +namespace djmfix {
    6.25 +namespace logging {
    6.26 +
    6.27 +enum class Level {
    6.28 +	SEVERE,
    6.29 +	WARNING,
    6.30 +	INFO,
    6.31 +	CONFIG,
    6.32 +	FINE,
    6.33 +	FINER,
    6.34 +	FINEST,
    6.35 +};
    6.36 +
    6.37 +class Logger {
    6.38 +public:
    6.39 +	virtual ~Logger() = default;
    6.40 +	virtual void log(Level level, const std::string& message) = 0;
    6.41 +};
    6.42 +
    6.43 +Logger* create(std::ostream& output, Level minLevel);
    6.44 +Logger* blackhole();
    6.45 +
    6.46 +}
    6.47 +}
    6.48 \ No newline at end of file
     7.1 --- a/Makefile	Mon Jan 04 13:38:08 2021 +0100
     7.2 +++ b/Makefile	Mon Jan 04 15:45:12 2021 +0100
     7.3 @@ -25,6 +25,6 @@
     7.4  
     7.5  .PHONY: all clean run
     7.6  
     7.7 -build/djm-fix: DJMFix.cpp DJMFix.h AlsaBridge.cpp AlsaBridge.h djm-fix.cpp
     7.8 +build/djm-fix: DJMFix.cpp DJMFix.h AlsaBridge.cpp AlsaBridge.h Logger.cpp Logger.h djm-fix.cpp
     7.9  	mkdir -p build
    7.10 -	g++ -o $@ DJMFix.cpp AlsaBridge.cpp djm-fix.cpp $(CXXFLAGS)
    7.11 +	g++ -o $@ DJMFix.cpp AlsaBridge.cpp Logger.cpp djm-fix.cpp $(CXXFLAGS)
     8.1 --- a/djm-fix.cpp	Mon Jan 04 13:38:08 2021 +0100
     8.2 +++ b/djm-fix.cpp	Mon Jan 04 15:45:12 2021 +0100
     8.3 @@ -24,12 +24,12 @@
     8.4  
     8.5  #include "DJMFix.h"
     8.6  #include "AlsaBridge.h"
     8.7 +#include "Logger.h"
     8.8  
     8.9  static std::atomic<bool> run{true};
    8.10  
    8.11  void interrupt(int signal) {
    8.12  	run = false;
    8.13 -	std::cerr << "interrupt()" << std::endl; // TODO: do not mess STDIO
    8.14  }
    8.15  
    8.16  /**
    8.17 @@ -80,19 +80,26 @@
    8.18   */
    8.19  
    8.20  int main(int argc, char**argv) {
    8.21 +	using L = djmfix::logging::Level;
    8.22 +	std::unique_ptr<djmfix::logging::Logger> logger(djmfix::logging::create(std::cerr, L::INFO));
    8.23  	try {
    8.24 +		logger->log(L::INFO, "djm-fix started");
    8.25  		std::string cardNamePattern = argc == 2 ? argv[1] : "Pioneer DJ.*";
    8.26  
    8.27  		signal(SIGINT, interrupt);
    8.28 -		std::unique_ptr<djmfix::DJMFix> djmFix(djmfix::create());
    8.29 -		std::unique_ptr<djmfix::alsa::AlsaBridge> alsaBridge(djmfix::alsa::create(djmFix.get(), cardNamePattern));
    8.30 +		std::unique_ptr<djmfix::DJMFix> djmFix(djmfix::create(logger.get()));
    8.31 +		std::unique_ptr<djmfix::alsa::AlsaBridge> alsaBridge(djmfix::alsa::create(djmFix.get(), cardNamePattern, logger.get()));
    8.32  
    8.33  		alsaBridge->start();
    8.34  		while (run) std::this_thread::sleep_for(std::chrono::milliseconds(100));
    8.35 +		
    8.36 +		std::cerr << std::endl;
    8.37 +		logger->log(L::INFO, "djm-fix stopping");
    8.38 +		
    8.39  		alsaBridge->stop();
    8.40  
    8.41  		return 0;
    8.42  	} catch (const std::exception& e) {
    8.43 -		std::cerr << "ERROR: " << e.what() << std::endl; // TODO: do not mess STDIO
    8.44 +		logger->log(L::SEVERE, e.what());
    8.45  	}
    8.46  }