<commit_before>/*
 * MIT License
 *
 * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "logger.h"
#include "onceToken.h"
#include "File.h"
#include <sys/stat.h>

namespace toolkit {

#define CLEAR_COLOR "\033[0m"
static const char *LOG_CONST_TABLE[][3] = {
        {"\033[44;37m", "\033[34m" , "T"},
        {"\033[42;37m", "\033[32m" , "D"},
        {"\033[46;37m", "\033[36m" , "I"},
        {"\033[43;37m", "\033[33m" , "W"},
        {"\033[41;37m", "\033[31m" , "E"}};

///////////////////Logger///////////////////
INSTANCE_IMP(Logger,exeName());

Logger::Logger(const string &loggerName) {
    _loggerName = loggerName;
}
Logger::~Logger() {
    _writer.reset();
    {
        LogContextCapturer(*this, LInfo, __FILE__, __FUNCTION__, __LINE__);
    }
    _channels.clear();
}

void Logger::add(const std::shared_ptr<LogChannel> &channel) {
    _channels[channel->name()] = channel;
}

void Logger::del(const string &name) {
    _channels.erase(name);
}

std::shared_ptr<LogChannel> Logger::get(const string &name) {
    auto it = _channels.find(name);
    if (it == _channels.end()) {
        return nullptr;
    }
    return it->second;
}

void Logger::setWriter(const std::shared_ptr<LogWriter> &writer) {
    _writer = writer;
}

void Logger::write(const LogContextPtr &logContext) {
    if (_writer) {
        _writer->write(logContext);
    }else{
        writeChannels(logContext);
    }
}

void Logger::setLevel(LogLevel level) {
    for (auto &chn : _channels) {
        chn.second->setLevel(level);
    }
}

void Logger::writeChannels(const LogContextPtr &logContext){
    for (auto &chn : _channels) {
        chn.second->write(*this,logContext);
    }
}

const string &Logger::getName() const{
    return _loggerName;
}
///////////////////LogContext///////////////////
LogContext::LogContext(LogLevel level,
        const char *file,
        const char *function,
        int line) :
        _level(level),
        _line(line),
        _file(file),
        _function(function) {
    gettimeofday(&_tv, NULL);
}

///////////////////AsyncLogWriter///////////////////
LogContextCapturer::LogContextCapturer(
        Logger &logger,
        LogLevel level,
        const char *file,
        const char *function,
        int line) :
        _logContext(new LogContext(level, file, function, line)),_logger(logger) {
}

LogContextCapturer::LogContextCapturer(const LogContextCapturer &that): _logContext(that._logContext),_logger(that._logger) {
    const_cast<LogContextPtr&>(that._logContext).reset();
}

LogContextCapturer::~LogContextCapturer() {
    *this << endl;
}

LogContextCapturer &LogContextCapturer::operator << (ostream &(*f)(ostream &)) {
    if (!_logContext) {
        return *this;
    }
    _logger.write(_logContext);
    _logContext.reset();
    return *this;
}

void LogContextCapturer::clear() {
    _logContext.reset();
}

///////////////////AsyncLogWriter///////////////////
AsyncLogWriter::AsyncLogWriter(Logger &logger) : _exit_flag(false),_logger(logger) {
    _thread = std::make_shared<thread>([this]() { this->run(); });
}

AsyncLogWriter::~AsyncLogWriter() {
    _exit_flag = true;
    _sem.post();
    _thread->join();
    flushAll();
}

void AsyncLogWriter::write(const LogContextPtr &logContext) {
    {
        lock_guard<mutex> lock(_mutex);
        _pending.emplace_back(logContext);
    }
    _sem.post();
}

void AsyncLogWriter::run() {
    while (!_exit_flag) {
        _sem.wait();
        flushAll();
    }
}
void AsyncLogWriter::flushAll(){
    List<LogContextPtr> tmp;
    {
        lock_guard<mutex> lock(_mutex);
        tmp.swap(_pending);
    }

    tmp.for_each([&](const LogContextPtr &ctx){
        _logger.writeChannels(ctx);
    });

}

///////////////////ConsoleChannel///////////////////

#ifdef ANDROID
#include <android/log.h>
#endif //ANDROID

ConsoleChannel::ConsoleChannel(const string &name, LogLevel level) : LogChannel(name, level) {}
ConsoleChannel:: ~ConsoleChannel() {}
void ConsoleChannel::write(const Logger &logger,const LogContextPtr &logContext)  {
    if (_level > logContext->_level) {
        return;
    }

#if defined(_WIN32) || defined(OS_IPHONE)
    format(logger,std::cout, logContext , false);
#elif defined(ANDROID)
    static android_LogPriority LogPriorityArr[10];
    static onceToken s_token([](){
        LogPriorityArr[LTrace] = ANDROID_LOG_VERBOSE;
        LogPriorityArr[LDebug] = ANDROID_LOG_DEBUG;
        LogPriorityArr[LInfo] = ANDROID_LOG_INFO;
        LogPriorityArr[LWarn] = ANDROID_LOG_WARN;
        LogPriorityArr[LError] = ANDROID_LOG_ERROR;
    }, nullptr);
    __android_log_print(LogPriorityArr[logContext->_level],"JNI","%s %s",logContext->_function,logContext->str().c_str());
#else
    format(logger,std::cout,logContext, true);
#endif
}

///////////////////SysLogChannel///////////////////
#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) &&  !defined(ANDROID))
#include <sys/syslog.h>
SysLogChannel::SysLogChannel(const string &name, LogLevel level) : LogChannel(name, level) {
}
SysLogChannel:: ~SysLogChannel() {
}
void SysLogChannel::write(const Logger &logger,const LogContextPtr &logContext)  {
    if (_level > logContext->_level) {
        return;
    }
    static int s_syslog_lev[10];
    static onceToken s_token([](){
        s_syslog_lev[LTrace] = LOG_DEBUG;
        s_syslog_lev[LDebug] = LOG_INFO;
        s_syslog_lev[LInfo] = LOG_NOTICE;
        s_syslog_lev[LWarn] = LOG_WARNING;
        s_syslog_lev[LError] = LOG_ERR;
    }, nullptr);

    syslog(s_syslog_lev[logContext->_level],"-> %s %d\r\n",logContext->_file,logContext->_line);
    syslog(s_syslog_lev[logContext->_level], "## %s %s | %s %s\r\n", printTime(logContext->_tv).data(),
           LOG_CONST_TABLE[logContext->_level][2], logContext->_function, logContext->str().c_str());
}

#endif//#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) &&  !defined(ANDROID))

///////////////////LogChannel///////////////////
LogChannel::LogChannel(const string &name, LogLevel level) : _name(name), _level(level) {}
LogChannel::~LogChannel(){}
const string &LogChannel::name() const { return _name; }
void LogChannel::setLevel(LogLevel level) { _level = level; }

std::string LogChannel::printTime(const timeval &tv) {
    time_t sec_tmp = tv.tv_sec;
    struct tm *tm = localtime(&sec_tmp);
    char buf[128];
    snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d.%03d",
             1900 + tm->tm_year,
             1 + tm->tm_mon,
             tm->tm_mday,
             tm->tm_hour,
             tm->tm_min,
             tm->tm_sec,
             (int) (tv.tv_usec / 1000));
    return buf;
}

void LogChannel::format(const Logger &logger,ostream &ost,const LogContextPtr & logContext, bool enableColor, bool enableDetail) {
    if (!enableDetail && logContext->str().empty()) {
        //没有任何信息打印
        return;
    }

    if (enableDetail) {
#if defined(_WIN32)
        ost << logger.getName() <<"(" << GetCurrentProcessId() << ") " << logContext->_file << " " << logContext->_line << endl;
#else
        ost << logger.getName() << "(" << getpid() << ") " << logContext->_file << " " << logContext->_line << endl;
#endif
    }

    if (enableColor) {
        ost << LOG_CONST_TABLE[logContext->_level][1];
    }

    ost << printTime(logContext->_tv) << " " << LOG_CONST_TABLE[logContext->_level][2] << " | ";

    if (enableDetail) {
        ost << logContext->_function << " ";
    }

    ost << logContext->str();

    if (enableColor) {
        ost << CLEAR_COLOR;
    }

    ost << endl;
}

///////////////////FileChannelBase///////////////////
FileChannelBase::FileChannelBase(const string &name, const string &path, LogLevel level) :
        LogChannel(name, level), _path(path) {}

FileChannelBase::~FileChannelBase() {
    close();
}

void FileChannelBase::write(const Logger &logger,const std::shared_ptr<LogContext> &logContext) {
    if (_level > logContext->_level) {
        return;
    }
    if (!_fstream.is_open()) {
        open();
    }
    format(logger, _fstream, logContext ,false);
}

void FileChannelBase::setPath(const string &path) {
    _path = path;
    open();
}

const string &FileChannelBase::path() const {
    return _path;
}

void FileChannelBase::open() {
    // Ensure a path was set
    if (_path.empty()) {
        throw runtime_error("Log file path must be set.");
    }
    // Open the file stream
    _fstream.close();
#if !defined(_WIN32)
    //创建文件夹
    File::createfile_path(_path.c_str(),S_IRWXO | S_IRWXG | S_IRWXU);
#else
    File::createfile_path(_path.c_str(),0);
#endif
    _fstream.open(_path.c_str(), ios::out | ios::app);
    // Throw on failure
    if (!_fstream.is_open()) {
        throw runtime_error("Failed to open log file: " + _path);
    }
}

void FileChannelBase::close() {
    _fstream.close();
}

///////////////////FileChannel///////////////////
FileChannel::FileChannel(const string &name, const string &dir, LogLevel level) : FileChannelBase(name, "", level) {
    _dir = dir;
    if(_dir.back() != '/'){
        _dir.append("/");
    }
}
FileChannel::~FileChannel() {}

static const auto s_second_per_day = 24 * 60 * 60;
uint64_t FileChannel::getDay(time_t second) {
    return second / s_second_per_day;
}

static string getLogFilePath(const string &dir,uint64_t day){
    time_t second = s_second_per_day * day;
    struct tm *tm = localtime(&second);
    char buf[32];
    snprintf(buf, sizeof(buf), "%d-%02d-%02d.log", 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday);
    return dir + buf;
}

void FileChannel::write(const Logger &logger, const shared_ptr<LogContext> &stream) {
    //这条日志所在第几天
    auto day = getDay(stream->_tv.tv_sec);
    if(day != _last_day){
        //这条日志是新的一天，记录这一天
        _last_day = day;
        //获取日志当天对应的文件，每天只有一个文件
        auto log_file = getLogFilePath(_dir,day);
        //记录所有的日志文件，以便后续删除老的日志
        _log_file_map.emplace(day,log_file);
        //打开新的日志文件
        setPath(log_file);
        clean();
    }
    //写日志
    FileChannelBase::write(logger,stream);
}

void FileChannel::clean(){
    //获取今天是第几天
    auto today = getDay(time(NULL));
    //遍历所有日志文件，删除老日志
    for(auto it = _log_file_map.begin(); it != _log_file_map.end() ; ){
        if(today - it->first <=  _log_max_day){
            //这个日志文件距今不超过_log_max_day天
            break;
        }
        //这个文件距今超过了_log_max_day天，则删除文件
        File::delete_file(it->second.data());
        //删除这条记录
        it = _log_file_map.erase(it);
    }
}

void FileChannel::setMaxDay(int max_day){
    _log_max_day = max_day > 1 ? max_day : 1;
}

} /* namespace toolkit */

<commit_msg>修正一天日志的偏差<commit_after>/*
 * MIT License
 *
 * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "logger.h"
#include "onceToken.h"
#include "File.h"
#include <sys/stat.h>

namespace toolkit {

#define CLEAR_COLOR "\033[0m"
static const char *LOG_CONST_TABLE[][3] = {
        {"\033[44;37m", "\033[34m" , "T"},
        {"\033[42;37m", "\033[32m" , "D"},
        {"\033[46;37m", "\033[36m" , "I"},
        {"\033[43;37m", "\033[33m" , "W"},
        {"\033[41;37m", "\033[31m" , "E"}};

///////////////////Logger///////////////////
INSTANCE_IMP(Logger,exeName());

Logger::Logger(const string &loggerName) {
    _loggerName = loggerName;
}
Logger::~Logger() {
    _writer.reset();
    {
        LogContextCapturer(*this, LInfo, __FILE__, __FUNCTION__, __LINE__);
    }
    _channels.clear();
}

void Logger::add(const std::shared_ptr<LogChannel> &channel) {
    _channels[channel->name()] = channel;
}

void Logger::del(const string &name) {
    _channels.erase(name);
}

std::shared_ptr<LogChannel> Logger::get(const string &name) {
    auto it = _channels.find(name);
    if (it == _channels.end()) {
        return nullptr;
    }
    return it->second;
}

void Logger::setWriter(const std::shared_ptr<LogWriter> &writer) {
    _writer = writer;
}

void Logger::write(const LogContextPtr &logContext) {
    if (_writer) {
        _writer->write(logContext);
    }else{
        writeChannels(logContext);
    }
}

void Logger::setLevel(LogLevel level) {
    for (auto &chn : _channels) {
        chn.second->setLevel(level);
    }
}

void Logger::writeChannels(const LogContextPtr &logContext){
    for (auto &chn : _channels) {
        chn.second->write(*this,logContext);
    }
}

const string &Logger::getName() const{
    return _loggerName;
}
///////////////////LogContext///////////////////
LogContext::LogContext(LogLevel level,
        const char *file,
        const char *function,
        int line) :
        _level(level),
        _line(line),
        _file(file),
        _function(function) {
    gettimeofday(&_tv, NULL);
}

///////////////////AsyncLogWriter///////////////////
LogContextCapturer::LogContextCapturer(
        Logger &logger,
        LogLevel level,
        const char *file,
        const char *function,
        int line) :
        _logContext(new LogContext(level, file, function, line)),_logger(logger) {
}

LogContextCapturer::LogContextCapturer(const LogContextCapturer &that): _logContext(that._logContext),_logger(that._logger) {
    const_cast<LogContextPtr&>(that._logContext).reset();
}

LogContextCapturer::~LogContextCapturer() {
    *this << endl;
}

LogContextCapturer &LogContextCapturer::operator << (ostream &(*f)(ostream &)) {
    if (!_logContext) {
        return *this;
    }
    _logger.write(_logContext);
    _logContext.reset();
    return *this;
}

void LogContextCapturer::clear() {
    _logContext.reset();
}

///////////////////AsyncLogWriter///////////////////
AsyncLogWriter::AsyncLogWriter(Logger &logger) : _exit_flag(false),_logger(logger) {
    _thread = std::make_shared<thread>([this]() { this->run(); });
}

AsyncLogWriter::~AsyncLogWriter() {
    _exit_flag = true;
    _sem.post();
    _thread->join();
    flushAll();
}

void AsyncLogWriter::write(const LogContextPtr &logContext) {
    {
        lock_guard<mutex> lock(_mutex);
        _pending.emplace_back(logContext);
    }
    _sem.post();
}

void AsyncLogWriter::run() {
    while (!_exit_flag) {
        _sem.wait();
        flushAll();
    }
}
void AsyncLogWriter::flushAll(){
    List<LogContextPtr> tmp;
    {
        lock_guard<mutex> lock(_mutex);
        tmp.swap(_pending);
    }

    tmp.for_each([&](const LogContextPtr &ctx){
        _logger.writeChannels(ctx);
    });

}

///////////////////ConsoleChannel///////////////////

#ifdef ANDROID
#include <android/log.h>
#endif //ANDROID

ConsoleChannel::ConsoleChannel(const string &name, LogLevel level) : LogChannel(name, level) {}
ConsoleChannel:: ~ConsoleChannel() {}
void ConsoleChannel::write(const Logger &logger,const LogContextPtr &logContext)  {
    if (_level > logContext->_level) {
        return;
    }

#if defined(_WIN32) || defined(OS_IPHONE)
    format(logger,std::cout, logContext , false);
#elif defined(ANDROID)
    static android_LogPriority LogPriorityArr[10];
    static onceToken s_token([](){
        LogPriorityArr[LTrace] = ANDROID_LOG_VERBOSE;
        LogPriorityArr[LDebug] = ANDROID_LOG_DEBUG;
        LogPriorityArr[LInfo] = ANDROID_LOG_INFO;
        LogPriorityArr[LWarn] = ANDROID_LOG_WARN;
        LogPriorityArr[LError] = ANDROID_LOG_ERROR;
    }, nullptr);
    __android_log_print(LogPriorityArr[logContext->_level],"JNI","%s %s",logContext->_function,logContext->str().c_str());
#else
    format(logger,std::cout,logContext, true);
#endif
}

///////////////////SysLogChannel///////////////////
#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) &&  !defined(ANDROID))
#include <sys/syslog.h>
SysLogChannel::SysLogChannel(const string &name, LogLevel level) : LogChannel(name, level) {
}
SysLogChannel:: ~SysLogChannel() {
}
void SysLogChannel::write(const Logger &logger,const LogContextPtr &logContext)  {
    if (_level > logContext->_level) {
        return;
    }
    static int s_syslog_lev[10];
    static onceToken s_token([](){
        s_syslog_lev[LTrace] = LOG_DEBUG;
        s_syslog_lev[LDebug] = LOG_INFO;
        s_syslog_lev[LInfo] = LOG_NOTICE;
        s_syslog_lev[LWarn] = LOG_WARNING;
        s_syslog_lev[LError] = LOG_ERR;
    }, nullptr);

    syslog(s_syslog_lev[logContext->_level],"-> %s %d\r\n",logContext->_file,logContext->_line);
    syslog(s_syslog_lev[logContext->_level], "## %s %s | %s %s\r\n", printTime(logContext->_tv).data(),
           LOG_CONST_TABLE[logContext->_level][2], logContext->_function, logContext->str().c_str());
}

#endif//#if defined(__MACH__) || ((defined(__linux) || defined(__linux__)) &&  !defined(ANDROID))

///////////////////LogChannel///////////////////
LogChannel::LogChannel(const string &name, LogLevel level) : _name(name), _level(level) {}
LogChannel::~LogChannel(){}
const string &LogChannel::name() const { return _name; }
void LogChannel::setLevel(LogLevel level) { _level = level; }

std::string LogChannel::printTime(const timeval &tv) {
    time_t sec_tmp = tv.tv_sec;
    struct tm *tm = localtime(&sec_tmp);
    char buf[128];
    snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d.%03d",
             1900 + tm->tm_year,
             1 + tm->tm_mon,
             tm->tm_mday,
             tm->tm_hour,
             tm->tm_min,
             tm->tm_sec,
             (int) (tv.tv_usec / 1000));
    return buf;
}

void LogChannel::format(const Logger &logger,ostream &ost,const LogContextPtr & logContext, bool enableColor, bool enableDetail) {
    if (!enableDetail && logContext->str().empty()) {
        //没有任何信息打印
        return;
    }

    if (enableDetail) {
#if defined(_WIN32)
        ost << logger.getName() <<"(" << GetCurrentProcessId() << ") " << logContext->_file << " " << logContext->_line << endl;
#else
        ost << logger.getName() << "(" << getpid() << ") " << logContext->_file << " " << logContext->_line << endl;
#endif
    }

    if (enableColor) {
        ost << LOG_CONST_TABLE[logContext->_level][1];
    }

    ost << printTime(logContext->_tv) << " " << LOG_CONST_TABLE[logContext->_level][2] << " | ";

    if (enableDetail) {
        ost << logContext->_function << " ";
    }

    ost << logContext->str();

    if (enableColor) {
        ost << CLEAR_COLOR;
    }

    ost << endl;
}

///////////////////FileChannelBase///////////////////
FileChannelBase::FileChannelBase(const string &name, const string &path, LogLevel level) :
        LogChannel(name, level), _path(path) {}

FileChannelBase::~FileChannelBase() {
    close();
}

void FileChannelBase::write(const Logger &logger,const std::shared_ptr<LogContext> &logContext) {
    if (_level > logContext->_level) {
        return;
    }
    if (!_fstream.is_open()) {
        open();
    }
    format(logger, _fstream, logContext ,false);
}

void FileChannelBase::setPath(const string &path) {
    _path = path;
    open();
}

const string &FileChannelBase::path() const {
    return _path;
}

void FileChannelBase::open() {
    // Ensure a path was set
    if (_path.empty()) {
        throw runtime_error("Log file path must be set.");
    }
    // Open the file stream
    _fstream.close();
#if !defined(_WIN32)
    //创建文件夹
    File::createfile_path(_path.c_str(),S_IRWXO | S_IRWXG | S_IRWXU);
#else
    File::createfile_path(_path.c_str(),0);
#endif
    _fstream.open(_path.c_str(), ios::out | ios::app);
    // Throw on failure
    if (!_fstream.is_open()) {
        throw runtime_error("Failed to open log file: " + _path);
    }
}

void FileChannelBase::close() {
    _fstream.close();
}

///////////////////FileChannel///////////////////
FileChannel::FileChannel(const string &name, const string &dir, LogLevel level) : FileChannelBase(name, "", level) {
    _dir = dir;
    if(_dir.back() != '/'){
        _dir.append("/");
    }
}
FileChannel::~FileChannel() {}

static const auto s_second_per_day = 24 * 60 * 60;
uint64_t FileChannel::getDay(time_t second) {
    return second / s_second_per_day;
}

static string getLogFilePath(const string &dir,uint64_t day){
    time_t second = s_second_per_day * day;
    struct tm *tm = localtime(&second);
    char buf[32];
    snprintf(buf, sizeof(buf), "%d-%02d-%02d.log", 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday);
    return dir + buf;
}

void FileChannel::write(const Logger &logger, const shared_ptr<LogContext> &stream) {
    //这条日志所在第几天
    auto day = getDay(stream->_tv.tv_sec);
    if(day != _last_day){
        //这条日志是新的一天，记录这一天
        _last_day = day;
        //获取日志当天对应的文件，每天只有一个文件
        auto log_file = getLogFilePath(_dir,day);
        //记录所有的日志文件，以便后续删除老的日志
        _log_file_map.emplace(day,log_file);
        //打开新的日志文件
        setPath(log_file);
        clean();
    }
    //写日志
    FileChannelBase::write(logger,stream);
}

void FileChannel::clean(){
    //获取今天是第几天
    auto today = getDay(time(NULL));
    //遍历所有日志文件，删除老日志
    for(auto it = _log_file_map.begin(); it != _log_file_map.end() ; ){
        if(today < it->first + _log_max_day){
            //这个日志文件距今不超过_log_max_day天
            break;
        }
        //这个文件距今超过了_log_max_day天，则删除文件
        File::delete_file(it->second.data());
        //删除这条记录
        it = _log_file_map.erase(it);
    }
}

void FileChannel::setMaxDay(int max_day){
    _log_max_day = max_day > 1 ? max_day : 1;
}

} /* namespace toolkit */

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 *
 */

#define OPEN_SSL_VERSION_MIN 0x10101000L
#ifdef HAS_OPENSSL
  #include <openssl/opensslv.h>
  #if OPENSSL_VERSION_NUMBER < OPEN_SSL_VERSION_MIN // defined on supported versions in evp.h header
    #undef HAS_OPENSSL
  #endif
#endif

#include <array>
#include <QLibrary>
#include "deconz/dbg_trace.h"
#include "random.h"
#include "scrypt.h"

#ifdef HAS_OPENSSL

#include <openssl/evp.h>
#include <openssl/kdf.h>

/*! KDF to scrypt the \p input.
 */
static int scryptDerive(const char *input, size_t inputLength, std::array<unsigned char, 64> &out, int N, int r, int p, const char *salt)
{
#ifdef Q_OS_WIN
    QLibrary libCrypto(QLatin1String("libcrypto-1_1.dll"));
    QLibrary libSsl(QLatin1String("libssl-1_1.dll"));
#else
    QLibrary libCrypto(QLatin1String("crypto"));
    QLibrary libSsl(QLatin1String("ssl"));
#endif

    unsigned long openSslVersion = 0;

    auto _OpenSSL_version_num = reinterpret_cast<unsigned long (*)(void)>(libCrypto.resolve("OpenSSL_version_num"));

    const auto EVP_PKEY_CTX_new_id = reinterpret_cast<EVP_PKEY_CTX *(*)(int id, ENGINE *e)>(libCrypto.resolve("EVP_PKEY_CTX_new_id"));
    const auto EVP_PKEY_derive_init = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx)>(libCrypto.resolve("EVP_PKEY_derive_init"));
    const auto EVP_PKEY_CTX_ctrl = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx, int keytype, int optype, int cmd, int p1, void *p2)>(libCrypto.resolve("EVP_PKEY_CTX_ctrl"));
    const auto EVP_PKEY_CTX_ctrl_uint64 = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx, int keytype, int optype, int cmd, uint64_t value)>(libCrypto.resolve("EVP_PKEY_CTX_ctrl_uint64"));
    const auto EVP_PKEY_derive = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen)>(libCrypto.resolve("EVP_PKEY_derive"));
    const auto EVP_PKEY_CTX_free = reinterpret_cast<void (*)(EVP_PKEY_CTX *ctx)>(libCrypto.resolve("EVP_PKEY_CTX_free"));

    if (_OpenSSL_version_num)
    {
        openSslVersion = _OpenSSL_version_num();
    }

    if (openSslVersion < OPEN_SSL_VERSION_MIN ||
            ! EVP_PKEY_CTX_new_id ||
            !EVP_PKEY_derive_init ||
            ! EVP_PKEY_CTX_ctrl ||
            ! EVP_PKEY_CTX_ctrl_uint64 ||
            ! EVP_PKEY_derive ||
            ! EVP_PKEY_CTX_free)
    {
        return -1;
    }

    int result = 0;
    EVP_PKEY_CTX *pctx;

    size_t outlen = out.size();
    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, NULL);

    result = pctx ? 0 : -1;

    if (result == 0 && EVP_PKEY_derive_init(pctx) <= 0)
    {
        result = -1;
    }

    if (result == 0 && EVP_PKEY_CTX_set1_pbe_pass(pctx, input, inputLength) <= 0)
    {
        result = -2;
    }

    if (result == 0 && EVP_PKEY_CTX_set1_scrypt_salt(pctx, salt, strlen(salt)) <= 0)
    {
        result = -3;
    }

    if (result == 0 && EVP_PKEY_CTX_set_scrypt_N(pctx, N) <= 0)
    {
        result = -4;
    }

    if (result == 0 && EVP_PKEY_CTX_set_scrypt_r(pctx, r) <= 0) {
        result = -5;
    }

    if (result == 0 && EVP_PKEY_CTX_set_scrypt_p(pctx, p) <= 0)
    {
        result = -6;
    }

    if (result == 0 && EVP_PKEY_derive(pctx, out.data(), &outlen) <= 0)
    {
        result = -7;
    }

    EVP_PKEY_CTX_free(pctx);

    return result;
}

/*! Encrypts the \p input with scrypt and given parameters.

    The \p salt should be created with \c CRYPTO_GenerateSalt.

    \returns a PHC encoded password hash
 */
std::string CRYPTO_ScryptPassword(const std::string &input, const std::string &salt, int N, int r, int p)
{
    std::string res;

    if (input.empty() || salt.empty())
    {
        return res;
    }

    std::array<unsigned char, 64> out;

    if (scryptDerive(input.data(), input.size(), out, N, r, p, salt.data()) != 0)
    {
        return res;
    }

    // PHC format
    // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md

    QByteArray base64Hash = QByteArray(reinterpret_cast<char*>(out.data()), out.size())
                              .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);

    QString s = QString("$scrypt$N=%1$r=%2$p=%3$%4$%5")
            .arg(N).arg(r).arg(p)
            .arg(salt.data(), base64Hash.constData());

    res = s.toStdString();

    return res;
}

/*! Parses the PHC encoded parameters, N, r, p, and salt, used for scrypt.
 */
bool CRYPTO_ParsePhcScryptParameters(const std::string &phcHash, ScryptParameters *param)
{
    if (!param || phcHash.empty() || strstr(phcHash.data(), "$scrypt") == nullptr)
    {
        return false;
    }

    const char *N = strstr(phcHash.data(), "$N=");
    const char *r = strstr(phcHash.data(), "$r=");
    const char *p = strstr(phcHash.data(), "$p=");

    if (!N || !r || !p)
    {
        return false;
    }

    const char *salt = strchr(p + 1, '$');

    if (!salt)
    {
        return false;
    }

    const char *saltEnd = strchr(salt + 1, '$');

    if (!saltEnd)
    {
        return false;
    }

    param->N = std::atoi(N + 3);
    param->r = std::atoi(r + 3);
    param->p = std::atoi(p + 3);
    param->salt = std::string(salt + 1, (saltEnd - salt) - 1);

    return param->N > 0 && param->r > 0 && param->p > 0 && !param->salt.empty();
}

/*! Returns true if the PHC encoded password hash matches the \p password.
 */
bool CRYPTO_ScryptVerify(const std::string &phcHash, const std::string &password)
{
    if (phcHash.empty() || password.empty())
    {
        return false;
    }

    ScryptParameters param;
    if (!CRYPTO_ParsePhcScryptParameters(phcHash, &param))
    {
        return false;
    }

    std::string hash2 = CRYPTO_ScryptPassword(password, param.salt, param.N, param.r, param.p);

    return hash2 == phcHash;
}

#else

// OpenSSL version too old
// return errors or empty results

bool CRYPTO_ParsePhcScryptParameters(const std::string &, ScryptParameters *)
{
    return false;
}

bool CRYPTO_ScryptVerify(const std::string &, const std::string &)
{
    return false;
}

std::string CRYPTO_ScryptPassword(const std::string &, const std::string &, int, int, int)
{
    return {};
}

#endif // HAS_OPENSSL

/*! Returns a base64 encoded cryptographic secure salt.
 */
std::string CRYPTO_GenerateSalt()
{
    unsigned char saltRandom[16]{0};
    CRYPTO_RandomBytes(saltRandom, sizeof(saltRandom));

    const auto salt = QByteArray::fromRawData(reinterpret_cast<char*>(saltRandom), sizeof(saltRandom))
                            .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);

    return salt.toStdString();
}
<commit_msg>Fix scrypt use of mixed signed/unsigned char* (#6370)<commit_after>/*
 * Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 *
 */

#define OPEN_SSL_VERSION_MIN 0x10101000L
#ifdef HAS_OPENSSL
  #include <openssl/opensslv.h>
  #if OPENSSL_VERSION_NUMBER < OPEN_SSL_VERSION_MIN // defined on supported versions in evp.h header
    #undef HAS_OPENSSL
  #endif
#endif

#include <array>
#include <QLibrary>
#include "deconz/dbg_trace.h"
#include "random.h"
#include "scrypt.h"

#ifdef HAS_OPENSSL

#include <openssl/evp.h>
#include <openssl/kdf.h>

/*! KDF to scrypt the \p input.
 */
static int scryptDerive(const char *input, size_t inputLength, std::array<unsigned char, 64> &out, int N, int r, int p, const unsigned char *salt, size_t saltlen)
{
#ifdef Q_OS_WIN
    QLibrary libCrypto(QLatin1String("libcrypto-1_1.dll"));
    QLibrary libSsl(QLatin1String("libssl-1_1.dll"));
#else
    QLibrary libCrypto(QLatin1String("crypto"));
    QLibrary libSsl(QLatin1String("ssl"));
#endif

    unsigned long openSslVersion = 0;

    auto _OpenSSL_version_num = reinterpret_cast<unsigned long (*)(void)>(libCrypto.resolve("OpenSSL_version_num"));

    const auto EVP_PKEY_CTX_new_id = reinterpret_cast<EVP_PKEY_CTX *(*)(int id, ENGINE *e)>(libCrypto.resolve("EVP_PKEY_CTX_new_id"));
    const auto EVP_PKEY_derive_init = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx)>(libCrypto.resolve("EVP_PKEY_derive_init"));
    const auto EVP_PKEY_CTX_ctrl = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx, int keytype, int optype, int cmd, int p1, void *p2)>(libCrypto.resolve("EVP_PKEY_CTX_ctrl"));
    const auto EVP_PKEY_CTX_ctrl_uint64 = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx, int keytype, int optype, int cmd, uint64_t value)>(libCrypto.resolve("EVP_PKEY_CTX_ctrl_uint64"));
    const auto EVP_PKEY_derive = reinterpret_cast<int (*)(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen)>(libCrypto.resolve("EVP_PKEY_derive"));
    const auto EVP_PKEY_CTX_free = reinterpret_cast<void (*)(EVP_PKEY_CTX *ctx)>(libCrypto.resolve("EVP_PKEY_CTX_free"));

    if (_OpenSSL_version_num)
    {
        openSslVersion = _OpenSSL_version_num();
    }

    if (openSslVersion < OPEN_SSL_VERSION_MIN ||
            ! EVP_PKEY_CTX_new_id ||
            !EVP_PKEY_derive_init ||
            ! EVP_PKEY_CTX_ctrl ||
            ! EVP_PKEY_CTX_ctrl_uint64 ||
            ! EVP_PKEY_derive ||
            ! EVP_PKEY_CTX_free)
    {
        return -1;
    }

    int result = 0;
    EVP_PKEY_CTX *pctx;

    size_t outlen = out.size();
    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, NULL);

    result = pctx ? 0 : -1;

    if (result == 0 && EVP_PKEY_derive_init(pctx) <= 0)
    {
        result = -1;
    }

    if (result == 0 && EVP_PKEY_CTX_set1_pbe_pass(pctx, input, inputLength) <= 0)
    {
        result = -2;
    }

    if (result == 0 && EVP_PKEY_CTX_set1_scrypt_salt(pctx, salt, saltlen) <= 0)
    {
        result = -3;
    }

    if (result == 0 && EVP_PKEY_CTX_set_scrypt_N(pctx, N) <= 0)
    {
        result = -4;
    }

    if (result == 0 && EVP_PKEY_CTX_set_scrypt_r(pctx, r) <= 0) {
        result = -5;
    }

    if (result == 0 && EVP_PKEY_CTX_set_scrypt_p(pctx, p) <= 0)
    {
        result = -6;
    }

    if (result == 0 && EVP_PKEY_derive(pctx, out.data(), &outlen) <= 0)
    {
        result = -7;
    }

    EVP_PKEY_CTX_free(pctx);

    return result;
}

/*! Encrypts the \p input with scrypt and given parameters.

    The \p salt should be created with \c CRYPTO_GenerateSalt.

    \returns a PHC encoded password hash
 */
std::string CRYPTO_ScryptPassword(const std::string &input, const std::string &salt, int N, int r, int p)
{
    std::string res;

    if (input.empty() || salt.empty())
    {
        return res;
    }

    std::array<unsigned char, 64> out;
    // We coerce the salt value to an unsigned char *, as it is no longer a string past this point.
    if (scryptDerive(input.data(), input.size(), out, N, r, p, (const unsigned char*)salt.data(), salt.length()) != 0)
    {
        return res;
    }

    // PHC format
    // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md

    QByteArray base64Hash = QByteArray(reinterpret_cast<char*>(out.data()), out.size())
                              .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);

    QString s = QString("$scrypt$N=%1$r=%2$p=%3$%4$%5")
            .arg(N).arg(r).arg(p)
            .arg(salt.data(), base64Hash.constData());

    res = s.toStdString();

    return res;
}

/*! Parses the PHC encoded parameters, N, r, p, and salt, used for scrypt.
 */
bool CRYPTO_ParsePhcScryptParameters(const std::string &phcHash, ScryptParameters *param)
{
    if (!param || phcHash.empty() || strstr(phcHash.data(), "$scrypt") == nullptr)
    {
        return false;
    }

    const char *N = strstr(phcHash.data(), "$N=");
    const char *r = strstr(phcHash.data(), "$r=");
    const char *p = strstr(phcHash.data(), "$p=");

    if (!N || !r || !p)
    {
        return false;
    }

    const char *salt = strchr(p + 1, '$');

    if (!salt)
    {
        return false;
    }

    const char *saltEnd = strchr(salt + 1, '$');

    if (!saltEnd)
    {
        return false;
    }

    param->N = std::atoi(N + 3);
    param->r = std::atoi(r + 3);
    param->p = std::atoi(p + 3);
    param->salt = std::string(salt + 1, (saltEnd - salt) - 1);

    return param->N > 0 && param->r > 0 && param->p > 0 && !param->salt.empty();
}

/*! Returns true if the PHC encoded password hash matches the \p password.
 */
bool CRYPTO_ScryptVerify(const std::string &phcHash, const std::string &password)
{
    if (phcHash.empty() || password.empty())
    {
        return false;
    }

    ScryptParameters param;
    if (!CRYPTO_ParsePhcScryptParameters(phcHash, &param))
    {
        return false;
    }

    std::string hash2 = CRYPTO_ScryptPassword(password, param.salt, param.N, param.r, param.p);

    return hash2 == phcHash;
}

#else

// OpenSSL version too old
// return errors or empty results

bool CRYPTO_ParsePhcScryptParameters(const std::string &, ScryptParameters *)
{
    return false;
}

bool CRYPTO_ScryptVerify(const std::string &, const std::string &)
{
    return false;
}

std::string CRYPTO_ScryptPassword(const std::string &, const std::string &, int, int, int)
{
    return {};
}

#endif // HAS_OPENSSL

/*! Returns a base64 encoded cryptographic secure salt.
 */
std::string CRYPTO_GenerateSalt()
{
    unsigned char saltRandom[16]{0};
    CRYPTO_RandomBytes(saltRandom, sizeof(saltRandom));

    const auto salt = QByteArray::fromRawData(reinterpret_cast<char*>(saltRandom), sizeof(saltRandom))
                            .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);

    return salt.toStdString();
}
<|endoftext|>


<commit_before>//
//  gunzip.cpp
//  RhoLib
//
//  Created by Alex Epifanoff on 23.09.14.
//
//

#include "gunzip.h"
#include "zlib.h"
#include <stdio.h>

namespace gunzip
{

  using namespace zlib;

  int UnzipGzip(const char* inputFilename, const char* outputFilename)
  {
    static const int LENGTH = 0x1000;

    gzFile input;
    FILE* output;

    input = gzopen( inputFilename, "r" );
    if ( 0 == input )
    {
      return -1;
    }

    output = fopen( outputFilename, "w" );
    if ( 0 == output )
    {
      gzclose(input);
      return -1;
    }

    int status = 0;
    unsigned char buffer[LENGTH];

    while (true)
    {
      int bytes_read = gzread(input, buffer, LENGTH);

      if (fwrite(buffer,sizeof(char),bytes_read,output) != bytes_read )
      {
        status = -1;
        break;
      }

      if (bytes_read < LENGTH )
      {
        if (!gzeof (input))
        {
            status = -1;
        }
        break;
      }
    }

    gzclose(input);
    fclose(output);

    return status;
  }

/*
  int UnzipGzip(const char* inputFilename, const char* outputFilename)
  {
      FILE* input;
      FILE* output;

      static const int CHUNK_SIZE = 0x4000;

      z_stream strm = {0};
      unsigned char in[CHUNK_SIZE];
      unsigned char out[CHUNK_SIZE];

      strm.zalloc = Z_NULL;
      strm.zfree = Z_NULL;
      strm.opaque = Z_NULL;
      strm.next_in = in;
      strm.avail_in = 0;
      strm.next_out = out;

      int status = 0;

      input = fopen(inputFilename, "rb" );
      if ( 0 == input ) goto unzip_gzip_error;

      output = fopen(outputFilename, "wb" );
      if ( 0 == output ) goto unzip_gzip_error;

      if ( inflateInit2(&strm) < 0 ) goto unzip_gzip_error;

      while (true) {
          int bytes_read;

          bytes_read = fread (in, sizeof (char), sizeof (in), input);
          if (ferror(input)) goto unzip_gzip_error;

          strm.avail_in = bytes_read;
          do {
              strm.avail_out = CHUNK_SIZE;
              if (inflate(&strm, Z_NO_FLUSH) < 0) goto unzip_gzip_error;

              fwrite(out, sizeof(char), strm.avail_out, output);

              if (ferror(output)) goto unzip_gzip_error;
          }

          while (strm.avail_out == 0);
          if (feof (input)) {
              inflateEnd (& strm);
              break;
          }
      }

      status |= fclose(input);
      status |= fclose(output);

      return (0==status?0:-1);

  unzip_gzip_error:

      inflateEnd (& strm);

      fclose(input);
      fclose(output);

      return -1;
  }
  */

}<commit_msg>gzip: change file open modes<commit_after>//
//  gunzip.cpp
//  RhoLib
//
//  Created by Alex Epifanoff on 23.09.14.
//
//

#include "gunzip.h"
#include "zlib.h"
#include <stdio.h>

namespace gunzip
{

  using namespace zlib;

  int UnzipGzip(const char* inputFilename, const char* outputFilename)
  {
    static const int LENGTH = 0x1000;

    gzFile input;
    FILE* output;

    input = gzopen( inputFilename, "rb" );
    if ( 0 == input )
    {
      return -1;
    }

    output = fopen( outputFilename, "wb" );
    if ( 0 == output )
    {
      gzclose(input);
      return -1;
    }

    int status = 0;
    unsigned char buffer[LENGTH];

    while (true)
    {
      int bytes_read = gzread(input, buffer, LENGTH);

      if (fwrite(buffer,sizeof(char),bytes_read,output) != bytes_read )
      {
        status = -1;
        break;
      }

      if (bytes_read < LENGTH )
      {
        if (!gzeof (input))
        {
            status = -1;
        }
        break;
      }
    }

    gzclose(input);
    fclose(output);

    return status;
  }

/*
  int UnzipGzip(const char* inputFilename, const char* outputFilename)
  {
      FILE* input;
      FILE* output;

      static const int CHUNK_SIZE = 0x4000;

      z_stream strm = {0};
      unsigned char in[CHUNK_SIZE];
      unsigned char out[CHUNK_SIZE];

      strm.zalloc = Z_NULL;
      strm.zfree = Z_NULL;
      strm.opaque = Z_NULL;
      strm.next_in = in;
      strm.avail_in = 0;
      strm.next_out = out;

      int status = 0;

      input = fopen(inputFilename, "rb" );
      if ( 0 == input ) goto unzip_gzip_error;

      output = fopen(outputFilename, "wb" );
      if ( 0 == output ) goto unzip_gzip_error;

      if ( inflateInit2(&strm) < 0 ) goto unzip_gzip_error;

      while (true) {
          int bytes_read;

          bytes_read = fread (in, sizeof (char), sizeof (in), input);
          if (ferror(input)) goto unzip_gzip_error;

          strm.avail_in = bytes_read;
          do {
              strm.avail_out = CHUNK_SIZE;
              if (inflate(&strm, Z_NO_FLUSH) < 0) goto unzip_gzip_error;

              fwrite(out, sizeof(char), strm.avail_out, output);

              if (ferror(output)) goto unzip_gzip_error;
          }

          while (strm.avail_out == 0);
          if (feof (input)) {
              inflateEnd (& strm);
              break;
          }
      }

      status |= fclose(input);
      status |= fclose(output);

      return (0==status?0:-1);

  unzip_gzip_error:

      inflateEnd (& strm);

      fclose(input);
      fclose(output);

      return -1;
  }
  */

}<|endoftext|>


<commit_before>
/*
 *  VideoStream.cpp
 *  sfeMovie project
 *
 *  Copyright (C) 2010-2015 Lucas Soltic
 *  lucas.soltic@orange.fr
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

#include "VideoStream.hpp"
#include "Utilities.hpp"
#include "Log.hpp"

namespace sfe
{
    VideoStream::VideoStream(AVFormatContext*& formatCtx, AVStream*& stream,
                             DataSource& dataSource, std::shared_ptr<Timer> timer, Delegate& delegate) :
    Stream(formatCtx ,stream, dataSource, timer),
    m_texture(),
    m_rawVideoFrame(nullptr),
    m_rgbaVideoBuffer(),
    m_rgbaVideoLinesize(),
    m_delegate(delegate),
    m_swsCtx(nullptr)
    {
        int err;

        for (int i = 0; i < 4;i++)
        {
            m_rgbaVideoBuffer[i] = nullptr;
            m_rgbaVideoLinesize[i] = 0;
        }

        m_rawVideoFrame = av_frame_alloc();
        CHECK(m_rawVideoFrame, "VideoStream() - out of memory");

        // RGBA video buffer
        err = av_image_alloc(m_rgbaVideoBuffer, m_rgbaVideoLinesize,
                             m_stream->codec->width, m_stream->codec->height,
                             PIX_FMT_RGBA, 1);
        CHECK(err >= 0, "VideoStream() - av_image_alloc() error");

        // SFML video frame
        err = m_texture.create(m_stream->codec->width, m_stream->codec->height);
        CHECK(err, "VideoStream() - sf::Texture::create() error");

        initRescaler();
    }

    VideoStream::~VideoStream()
    {
        if (m_rawVideoFrame)
        {
            av_frame_free(&m_rawVideoFrame);
        }

        if (m_rgbaVideoBuffer[0])
        {
            av_freep(&m_rgbaVideoBuffer[0]);
        }

        if (m_swsCtx)
        {
            sws_freeContext(m_swsCtx);
        }
    }

    MediaType VideoStream::getStreamKind() const
    {
        return Video;
    }

    sf::Vector2i VideoStream::getFrameSize() const
    {
        return sf::Vector2i(m_stream->codec->width, m_stream->codec->height);
    }

    float VideoStream::getFrameRate() const
    {
        return static_cast<float>(av_q2d(av_guess_frame_rate(m_formatCtx, m_stream, nullptr)));
    }

    sf::Texture& VideoStream::getVideoTexture()
    {
        return m_texture;
    }

    void VideoStream::update()
    {
        sf::Time gap;
        bool couldComputeGap = false;
        while (getStatus() == Playing && (couldComputeGap = getSynchronizationGap(gap)) &&
               gap < sf::Time::Zero)
        {
            if (!onGetData(m_texture))
            {
                setStatus(Stopped);
            }
            else
            {
                static const sf::Time skipFrameThreshold(sf::milliseconds(50));
                if (getSynchronizationGap(gap) && gap + skipFrameThreshold >= sf::Time::Zero)
                    m_delegate.didUpdateVideo(*this, m_texture);
            }
        }

        if (! couldComputeGap)
        {
            setStatus(Stopped);
        }
    }

    void VideoStream::flushBuffers()
    {
        m_codecBufferingDelays.clear();
        Stream::flushBuffers();
    }

    bool VideoStream::fastForward(sf::Time targetPosition)
    {
        sf::Time position;
        bool couldGetPosition = false;

        while ((couldGetPosition = computeEncodedPosition(position)) && position < targetPosition)
        {
            // We HAVE to decode the frames to get a full image when we reach the target position
            if (! onGetData(m_texture))
            {
                sfeLogError("Error while fast forwarding video stream up to position " +
                            s(targetPosition.asSeconds()) + "s");
                return false;
            }
        }

        if (! couldGetPosition)
        {
            sfeLogWarning("Could not get video stream position, seeking may be innacurate");
        }

        return true;
    }

    void VideoStream::preload()
    {
        sfeLogDebug("Preload video image");
        onGetData(m_texture);
    }

    bool VideoStream::onGetData(sf::Texture& texture)
    {
        AVPacket* packet = popEncodedData();
        bool gotFrame = false;
        bool goOn = false;

        if (packet)
        {
            goOn = true;

            while (!gotFrame && packet && goOn)
            {
                bool needsMoreDecoding = false;

                CHECK(packet != nullptr, "inconsistency error");
                goOn = decodePacket(packet, m_rawVideoFrame, gotFrame, needsMoreDecoding);

                if (gotFrame)
                {
                    rescale(m_rawVideoFrame, m_rgbaVideoBuffer, m_rgbaVideoLinesize);
                    texture.update(m_rgbaVideoBuffer[0]);
                }

                if (!gotFrame && goOn)
                {
                    // Decoding went fine but did not produce an image. This means the decoder is working in
                    // a pipelined way and wants more packets to output a full image. When the first full image will
                    // be generated, the encoded data queue head pts will be late compared to the generated image pts
                    // To take that into account we accumulate this time difference for reuse in getSynchronizationGap()
                    m_codecBufferingDelays.push_back(packetDuration(packet));

                    if (m_codecBufferingDelays.size() > m_stream->codec->delay)
                        m_codecBufferingDelays.pop_front();

                    sfeLogDebug("Accumulated video codec time: " + s(codecBufferingDelay().asMilliseconds()) + "ms");
                }

                if (needsMoreDecoding)
                {
                    prependEncodedData(packet);
                }
                else
                {
                    av_free_packet(packet);
                    av_free(packet);
                }

                if (!gotFrame && goOn)
                {
                    packet = popEncodedData();
                }
            }
        }

        return goOn;
    }

    bool VideoStream::getSynchronizationGap(sf::Time& gap)
    {
        sf::Time position;
        if (computeEncodedPosition(position))
        {
            gap = (position - codecBufferingDelay()) - m_timer->getOffset();
            return true;
        }
        else
        {
            sfeLogDebug("failed computing synchronization gap for video stream, it may become out of sync");
            return false;
        }
    }

    bool VideoStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding)
    {
        int gotPicture = 0;
        needsMoreDecoding = false;

        int decodedLength = avcodec_decode_video2(m_stream->codec, outputFrame, &gotPicture, packet);
        gotFrame = (gotPicture != 0);

        if (decodedLength > 0 || gotFrame)
        {
            if (decodedLength < packet->size)
            {
                needsMoreDecoding = true;
                packet->data += decodedLength;
                packet->size -= decodedLength;
            }

            return true;
        }
        else
        {
            return false;
        }
    }

    void VideoStream::initRescaler()
    {
        /* create scaling context */
        int algorithm = SWS_FAST_BILINEAR;

        if (getFrameSize().x % 8 != 0 && getFrameSize().x * getFrameSize().y < 500000)
        {
            algorithm |= SWS_ACCURATE_RND;
        }

        m_swsCtx = sws_getCachedContext(nullptr, m_stream->codec->width, m_stream->codec->height, m_stream->codec->pix_fmt,
                                        m_stream->codec->width, m_stream->codec->height, PIX_FMT_RGBA,
                                        algorithm, nullptr, nullptr, nullptr);
        CHECK(m_swsCtx, "VideoStream::initRescaler() - sws_getContext() error");
    }

    void VideoStream::rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4])
    {
        CHECK(frame, "VideoStream::rescale() - invalid argument");
        sws_scale(m_swsCtx, frame->data, frame->linesize, 0, frame->height, outVideoBuffer, outVideoLinesize);
    }

    void VideoStream::willPlay(const Timer &timer)
    {
        Stream::willPlay(timer);
        if (getStatus() == Stopped)
        {
            preload();
        }
    }

    sf::Time VideoStream::codecBufferingDelay() const
    {
        sf::Time delay;
        for (const sf::Time& packetDelay : m_codecBufferingDelays)
            delay += packetDelay;

        return delay;
    }
}
<commit_msg>fix bug when changing video offset while paused<commit_after>
/*
 *  VideoStream.cpp
 *  sfeMovie project
 *
 *  Copyright (C) 2010-2015 Lucas Soltic
 *  lucas.soltic@orange.fr
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

#include "VideoStream.hpp"
#include "Utilities.hpp"
#include "Log.hpp"

namespace sfe
{
    VideoStream::VideoStream(AVFormatContext*& formatCtx, AVStream*& stream,
                             DataSource& dataSource, std::shared_ptr<Timer> timer, Delegate& delegate) :
    Stream(formatCtx ,stream, dataSource, timer),
    m_texture(),
    m_rawVideoFrame(nullptr),
    m_rgbaVideoBuffer(),
    m_rgbaVideoLinesize(),
    m_delegate(delegate),
    m_swsCtx(nullptr)
    {
        int err;

        for (int i = 0; i < 4;i++)
        {
            m_rgbaVideoBuffer[i] = nullptr;
            m_rgbaVideoLinesize[i] = 0;
        }

        m_rawVideoFrame = av_frame_alloc();
        CHECK(m_rawVideoFrame, "VideoStream() - out of memory");

        // RGBA video buffer
        err = av_image_alloc(m_rgbaVideoBuffer, m_rgbaVideoLinesize,
                             m_stream->codec->width, m_stream->codec->height,
                             PIX_FMT_RGBA, 1);
        CHECK(err >= 0, "VideoStream() - av_image_alloc() error");

        // SFML video frame
        err = m_texture.create(m_stream->codec->width, m_stream->codec->height);
        CHECK(err, "VideoStream() - sf::Texture::create() error");

        initRescaler();
    }

    VideoStream::~VideoStream()
    {
        if (m_rawVideoFrame)
        {
            av_frame_free(&m_rawVideoFrame);
        }

        if (m_rgbaVideoBuffer[0])
        {
            av_freep(&m_rgbaVideoBuffer[0]);
        }

        if (m_swsCtx)
        {
            sws_freeContext(m_swsCtx);
        }
    }

    MediaType VideoStream::getStreamKind() const
    {
        return Video;
    }

    sf::Vector2i VideoStream::getFrameSize() const
    {
        return sf::Vector2i(m_stream->codec->width, m_stream->codec->height);
    }

    float VideoStream::getFrameRate() const
    {
        return static_cast<float>(av_q2d(av_guess_frame_rate(m_formatCtx, m_stream, nullptr)));
    }

    sf::Texture& VideoStream::getVideoTexture()
    {
        return m_texture;
    }

    void VideoStream::update()
    {
        sf::Time gap;
        bool couldComputeGap = false;
        while (getStatus() == Playing && (couldComputeGap = getSynchronizationGap(gap)) &&
               gap < sf::Time::Zero)
        {
            if (!onGetData(m_texture))
            {
                setStatus(Stopped);
            }
            else
            {
                static const sf::Time skipFrameThreshold(sf::milliseconds(50));
                if (getSynchronizationGap(gap) && gap + skipFrameThreshold >= sf::Time::Zero)
                    m_delegate.didUpdateVideo(*this, m_texture);
            }
        }

        if (! couldComputeGap && getStatus() == Playing)
        {
            setStatus(Stopped);
        }
    }

    void VideoStream::flushBuffers()
    {
        m_codecBufferingDelays.clear();
        Stream::flushBuffers();
    }

    bool VideoStream::fastForward(sf::Time targetPosition)
    {
        sf::Time position;
        bool couldGetPosition = false;

        while ((couldGetPosition = computeEncodedPosition(position)) && position < targetPosition)
        {
            // We HAVE to decode the frames to get a full image when we reach the target position
            if (! onGetData(m_texture))
            {
                sfeLogError("Error while fast forwarding video stream up to position " +
                            s(targetPosition.asSeconds()) + "s");
                return false;
            }
        }

        if (! couldGetPosition)
        {
            sfeLogWarning("Could not get video stream position, seeking may be innacurate");
        }

        return true;
    }

    void VideoStream::preload()
    {
        sfeLogDebug("Preload video image");
        onGetData(m_texture);
    }

    bool VideoStream::onGetData(sf::Texture& texture)
    {
        AVPacket* packet = popEncodedData();
        bool gotFrame = false;
        bool goOn = false;

        if (packet)
        {
            goOn = true;

            while (!gotFrame && packet && goOn)
            {
                bool needsMoreDecoding = false;

                CHECK(packet != nullptr, "inconsistency error");
                goOn = decodePacket(packet, m_rawVideoFrame, gotFrame, needsMoreDecoding);

                if (gotFrame)
                {
                    rescale(m_rawVideoFrame, m_rgbaVideoBuffer, m_rgbaVideoLinesize);
                    texture.update(m_rgbaVideoBuffer[0]);
                }

                if (!gotFrame && goOn)
                {
                    // Decoding went fine but did not produce an image. This means the decoder is working in
                    // a pipelined way and wants more packets to output a full image. When the first full image will
                    // be generated, the encoded data queue head pts will be late compared to the generated image pts
                    // To take that into account we accumulate this time difference for reuse in getSynchronizationGap()
                    m_codecBufferingDelays.push_back(packetDuration(packet));

                    if (m_codecBufferingDelays.size() > m_stream->codec->delay)
                        m_codecBufferingDelays.pop_front();

                    sfeLogDebug("Accumulated video codec time: " + s(codecBufferingDelay().asMilliseconds()) + "ms");
                }

                if (needsMoreDecoding)
                {
                    prependEncodedData(packet);
                }
                else
                {
                    av_free_packet(packet);
                    av_free(packet);
                }

                if (!gotFrame && goOn)
                {
                    packet = popEncodedData();
                }
            }
        }

        return goOn;
    }

    bool VideoStream::getSynchronizationGap(sf::Time& gap)
    {
        sf::Time position;
        if (computeEncodedPosition(position))
        {
            gap = (position - codecBufferingDelay()) - m_timer->getOffset();
            return true;
        }
        else
        {
            sfeLogDebug("failed computing synchronization gap for video stream, it may become out of sync");
            return false;
        }
    }

    bool VideoStream::decodePacket(AVPacket* packet, AVFrame* outputFrame, bool& gotFrame, bool& needsMoreDecoding)
    {
        int gotPicture = 0;
        needsMoreDecoding = false;

        int decodedLength = avcodec_decode_video2(m_stream->codec, outputFrame, &gotPicture, packet);
        gotFrame = (gotPicture != 0);

        if (decodedLength > 0 || gotFrame)
        {
            if (decodedLength < packet->size)
            {
                needsMoreDecoding = true;
                packet->data += decodedLength;
                packet->size -= decodedLength;
            }

            return true;
        }
        else
        {
            return false;
        }
    }

    void VideoStream::initRescaler()
    {
        /* create scaling context */
        int algorithm = SWS_FAST_BILINEAR;

        if (getFrameSize().x % 8 != 0 && getFrameSize().x * getFrameSize().y < 500000)
        {
            algorithm |= SWS_ACCURATE_RND;
        }

        m_swsCtx = sws_getCachedContext(nullptr, m_stream->codec->width, m_stream->codec->height, m_stream->codec->pix_fmt,
                                        m_stream->codec->width, m_stream->codec->height, PIX_FMT_RGBA,
                                        algorithm, nullptr, nullptr, nullptr);
        CHECK(m_swsCtx, "VideoStream::initRescaler() - sws_getContext() error");
    }

    void VideoStream::rescale(AVFrame* frame, uint8_t* outVideoBuffer[4], int outVideoLinesize[4])
    {
        CHECK(frame, "VideoStream::rescale() - invalid argument");
        sws_scale(m_swsCtx, frame->data, frame->linesize, 0, frame->height, outVideoBuffer, outVideoLinesize);
    }

    void VideoStream::willPlay(const Timer &timer)
    {
        Stream::willPlay(timer);
        if (getStatus() == Stopped)
        {
            preload();
        }
    }

    sf::Time VideoStream::codecBufferingDelay() const
    {
        sf::Time delay;
        for (const sf::Time& packetDelay : m_codecBufferingDelays)
            delay += packetDelay;

        return delay;
    }
}
<|endoftext|>


<commit_before>
#include "PacketListPane.h"
#include "protocol.h"
#include "TextPane.h"

Packet::~Packet()
{
  for (size_t i=0; i<details.size(); i++) {
    delete details[i];
  }
}

void Packet::analyze(const uint8_t* ptr, size_t len)
{
  using namespace slankdev;
  details.clear();

  Ethernet* eth = new Ethernet(ptr, len);
  details.push_back(eth);
  len -= eth->headerlen();
  ptr += eth->headerlen();
  uint16_t type = eth->type();
  src = eth->src();
  dst = eth->dst();
  protocol = "Ether";
  summary  = fs("Ethernet packet.type=0x%04x", eth->type());

  switch (type) {
    case 0x800:
      {
        IP* ip = new IP(ptr, len);
        details.push_back(ip);
        len -= ip->headerlen();
        ptr += ip->headerlen();
        uint8_t proto = ip->protocol();
        src = ip->src();
        dst = ip->dst();
        protocol = "IPv4";
        summary = fs("protocol=%u\n", ip->protocol());

        switch (proto) {
          case 1:
            {
              ICMP* icmp = new ICMP(ptr, len);
              details.push_back(icmp);
              len -= icmp->headerlen();
              ptr += icmp->headerlen();
              protocol = "ICMP";
              summary = fs("type=%u code=%u", icmp->type(), icmp->code());
              break;
            }
          case 17:
            {
              UDP* udp = new UDP(ptr, len);
              details.push_back(udp);
              len -= udp->headerlen();
              ptr += udp->headerlen();
              protocol = "UDP";
              summary = fs("%u -> %u", udp->src(), udp->dst());
              break;
            }
          case 6:
            {
              TCP* tcp = new TCP(ptr, len);
              details.push_back(tcp);
              len -= tcp->headerlen();
              ptr += tcp->headerlen();
              protocol = "TCP";
              summary = fs("%u -> %u", tcp->src(), tcp->dst());
              break;
            }
        }
        break;
      }
    case 0x86dd:
      {
        /*
         * Analyze IPv6
         */
        protocol = "IPv6";
        break;
      }
    case 0x0806:
      {
        /*
         * Analyze ARP
         */
        ARP* arp = new ARP(ptr, len);
        details.push_back(arp);
        len -= arp->headerlen();
        ptr += arp->headerlen();
        protocol = "ARP";
        break;
      }
  }
  if (len > 0) details.push_back(new Binary(ptr, len));
}

Packet::Packet(const void* p, size_t l, uint64_t t, size_t n)
  : len(l), time(t),  number(n)
{
  /*
   * Init Detail Pane Information
   */
  analyze(reinterpret_cast<const uint8_t*>(p), l);

  /*
   * Init Binary Pane Information
   */
  binarys.clear();
  using namespace slankdev;

  const void* buffer = p;
  ssize_t bufferlen  = l;

  const uint8_t *data = reinterpret_cast<const uint8_t*>(buffer);
  size_t row = 0;

  std::string line;
  while (bufferlen > 0) {
    line.clear();

    line += fs(" %04zx   ", row);

    size_t n = std::min(bufferlen, ssize_t(16));

    for (size_t i = 0; i < n; i++) {
      if (i == 8) { line += " "; }
      line += fs(" %02x", data[i]);
    }
    for (size_t i = n; i < 16; i++) { line += "   "; }
    line += "   ";
    for (size_t i = 0; i < n; i++) {
      if (i == 8) { line += "  "; }
      uint8_t c = data[i];
      if (!(0x20 <= c && c <= 0x7e)) c = '.';
      line += fs("%c", c);
    }
    binarys.push_back(line);
    bufferlen -= n;
    data += n;
    row  += n;
  }
}

/*
 * Class Member Function Implementation
 */
std::string Packet::to_str() const
{
  using namespace slankdev;
  char sstr[1000];
  sprintf(sstr, "%5zd %-13ld %-20s %-20s %-6s %5zd %-10s" , number, time,
          src.c_str(),
          dst.c_str(),
          protocol.c_str(), len,
          summary.c_str());
  return sstr;
}

void PacketListPane::key_input(int c)
{
  if (c == 'j' || c == KEY_DOWN) {
    cursor_down();
  } else if (c == 'k' || c == KEY_UP) {
    cursor_up();
  }
}

PacketListPane::PacketListPane(size_t _x, size_t _y, size_t _w, size_t _h)
  : PaneInterface(_x, _y, _w, _h), cursor(0), start_idx(0) {}

void PacketListPane::cursor_down()
{
  if (cursor + 1 < packets.size()) {
    if (cursor - start_idx + 2 > h) scroll_down();
    cursor++;
  }
}
void PacketListPane::cursor_up()
{
  if (cursor > 0) {
    if (cursor - 1 < start_idx) scroll_up();
    cursor--;
  }
}

<commit_msg>Refact<commit_after>
#include "PacketListPane.h"
#include "protocol.h"
#include "TextPane.h"

Packet::~Packet()
{
  for (size_t i=0; i<details.size(); i++) {
    delete details[i];
  }
}

void Packet::analyze(const uint8_t* ptr, size_t len)
{
  using namespace slankdev;
  details.clear();

  Ethernet* eth = new Ethernet(ptr, len);
  details.push_back(eth);
  len -= eth->headerlen();
  ptr += eth->headerlen();
  uint16_t type = eth->type();
  src = eth->src();
  dst = eth->dst();
  protocol = "Ether";
  summary  = fs("Ethernet packet.type=0x%04x", eth->type());

  switch (type) {
    case 0x800:
      {
        IP* ip = new IP(ptr, len);
        details.push_back(ip);
        len -= ip->headerlen();
        ptr += ip->headerlen();
        uint8_t proto = ip->protocol();
        src = ip->src();
        dst = ip->dst();
        protocol = "IPv4";
        summary = fs("protocol=%u\n", ip->protocol());

        switch (proto) {
          case 1:
            {
              ICMP* icmp = new ICMP(ptr, len);
              details.push_back(icmp);
              len -= icmp->headerlen();
              ptr += icmp->headerlen();
              protocol = "ICMP";
              summary = fs("type=%u code=%u", icmp->type(), icmp->code());
              break;
            }
          case 17:
            {
              UDP* udp = new UDP(ptr, len);
              details.push_back(udp);
              len -= udp->headerlen();
              ptr += udp->headerlen();
              protocol = "UDP";
              summary = fs("%u -> %u", udp->src(), udp->dst());
              break;
            }
          case 6:
            {
              TCP* tcp = new TCP(ptr, len);
              details.push_back(tcp);
              len -= tcp->headerlen();
              ptr += tcp->headerlen();
              protocol = "TCP";
              summary = fs("%u -> %u", tcp->src(), tcp->dst());
              break;
            }
        }
        break;
      }
    case 0x86dd:
      {
        /*
         * Analyze IPv6
         */
        protocol = "IPv6";
        break;
      }
    case 0x0806:
      {
        /*
         * Analyze ARP
         */
        ARP* arp = new ARP(ptr, len);
        details.push_back(arp);
        len -= arp->headerlen();
        ptr += arp->headerlen();
        protocol = "ARP";
        break;
      }
  }
  if (len > 0) details.push_back(new Binary(ptr, len));
}

Packet::Packet(const void* p, size_t l, uint64_t t, size_t n)
  : len(l), time(t),  number(n)
{
  /*
   * Init Detail Pane Information
   */
  analyze(reinterpret_cast<const uint8_t*>(p), l);

  /*
   * Init Binary Pane Information
   */
  binarys.clear();
  using namespace slankdev;

  const void* buffer = p;
  ssize_t bufferlen  = l;

  const uint8_t *data = reinterpret_cast<const uint8_t*>(buffer);
  size_t row = 0;

  std::string line;
  while (bufferlen > 0) {
    line.clear();

    line += fs(" %04zx   ", row);

    size_t n = std::min(bufferlen, ssize_t(16));

    for (size_t i = 0; i < n; i++) {
      if (i == 8) { line += " "; }
      line += fs(" %02x", data[i]);
    }
    for (size_t i = n; i < 16; i++) { line += "   "; }
    line += "   ";
    for (size_t i = 0; i < n; i++) {
      if (i == 8) { line += "  "; }
      uint8_t c = data[i];
      if (!(0x20 <= c && c <= 0x7e)) c = '.';
      line += fs("%c", c);
    }
    binarys.push_back(line);
    bufferlen -= n;
    data += n;
    row  += n;
  }
}

/*
 * Class Member Function Implementation
 */
std::string Packet::to_str() const
{
  using namespace slankdev;
  char sstr[1000];
  sprintf(sstr, "%5zd %-13lld %-20s %-20s %-6s %5zd %-10s" , number, time,
          src.c_str(),
          dst.c_str(),
          protocol.c_str(), len,
          summary.c_str());
  return sstr;
}

void PacketListPane::key_input(int c)
{
  if (c == 'j' || c == KEY_DOWN) {
    cursor_down();
  } else if (c == 'k' || c == KEY_UP) {
    cursor_up();
  }
}

PacketListPane::PacketListPane(size_t _x, size_t _y, size_t _w, size_t _h)
  : PaneInterface(_x, _y, _w, _h), cursor(0), start_idx(0) {}

void PacketListPane::cursor_down()
{
  if (cursor + 1 < packets.size()) {
    if (cursor - start_idx + 2 > h) scroll_down();
    cursor++;
  }
}
void PacketListPane::cursor_up()
{
  if (cursor > 0) {
    if (cursor - 1 < start_idx) scroll_up();
    cursor--;
  }
}

<|endoftext|>


<commit_before>#include "imguruploader.hpp"

#include <QBuffer>
#include <QJsonObject>
#include <QJsonValue>
#include <io/ioutils.hpp>
#include <notifications.hpp>
#include <screenshotutil.hpp>

void ImgurUploader::doUpload(QPixmap *pixmap) {
    QByteArray byteArray;
    QBuffer buffer(&byteArray);
    pixmap->save(&buffer, "PNG");
    ioutils::postJson(QUrl("https://api.imgur.com/3/image"),
                      QList<QPair<QString, QString>>()
                      << QPair<QString, QString>("Content-Type", "application/x-www-form-urlencoded")
                      << QPair<QString, QString>("Authorization", "Client-ID 8a98f183fc895da"),
                      byteArray, [](QJsonDocument res, QNetworkReply *) {
                          QString result = res.object()["data"].toObject()["link"].toString();
                          screenshotutil::toClipboard(result);
                          notifications::notify("KShare imgur Uploader ",
                                                result.isEmpty() ? "Failed upload!" : "Upload done, but result empty!");
                      });
}
<commit_msg>Turns out this was broken for a while now<commit_after>#include "imguruploader.hpp"

#include <QBuffer>
#include <QJsonObject>
#include <QJsonValue>
#include <io/ioutils.hpp>
#include <notifications.hpp>
#include <screenshotutil.hpp>

void ImgurUploader::doUpload(QPixmap *pixmap) {
    QByteArray byteArray;
    QBuffer buffer(&byteArray);
    pixmap->save(&buffer, "PNG");
    ioutils::postJson(QUrl("https://api.imgur.com/3/image"),
                      QList<QPair<QString, QString>>()
                      << QPair<QString, QString>("Content-Type", "application/x-www-form-urlencoded")
                      << QPair<QString, QString>("Authorization", "Client-ID 8a98f183fc895da"),
                      byteArray, [](QJsonDocument res, QNetworkReply *) {
                          QString result = res.object()["data"].toObject()["link"].toString();
                          screenshotutil::toClipboard(result);
                          notifications::notify("KShare imgur Uploader ",
                                                result.isEmpty() ? "Failed upload!" : "Uploaded to imgur!");
                      });
}
<|endoftext|>


<commit_before>/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011  Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#ifndef CLIENT_ONLY

#include "action_header.h"
#include "../server_settings.h"
#include "../os_functions.h"
#include "../server_status.h"

ACTION_IMPL(status)
{
	Helper helper(tid, &GET, &PARAMS);
	JSON::Object ret;

	std::string rights=helper.getRights("status");
	std::vector<int> clientids;
	IDatabase *db=helper.getDatabase();
	if(rights!="all" && rights!="none" )
	{
		std::vector<std::string> s_clientid;
		Tokenize(rights, s_clientid, ",");
		for(size_t i=0;i<s_clientid.size();++i)
		{
			clientids.push_back(atoi(s_clientid[i].c_str()));
		}
	}

	SUser *session=helper.getSession();
	if(session!=NULL && session->id==-1) return;
	if(session!=NULL && (rights=="all" || !clientids.empty()) )
	{
		{
			ServerSettings settings(db);
			if(!os_directory_exists(settings.getSettings()->backupfolder) || !os_directory_exists(settings.getSettings()->backupfolder_uncompr) || settings.getSettings()->backupfolder.empty())
			{
				ret.set("dir_error", true);
			}
			else if(!os_directory_exists(settings.getSettings()->backupfolder+os_file_sep()+L"clients") && !os_create_dir(settings.getSettings()->backupfolder+os_file_sep()+L"clients") )
			{
				ret.set("dir_error" ,true);
			}
		}

		bool details=false;
		if(GET.find(L"details")!=GET.end())
		{
			details=true;
			ret.set("details", true);
		}
		std::wstring hostname=GET[L"hostname"];
		if(!hostname.empty())
		{
			if(GET[L"remove"]==L"true")
			{
				IQuery *q=db->Prepare("DELETE FROM extra_clients WHERE id=?");
				q->Bind(hostname);
				q->Write();
				q->Reset();
			}
			else
			{
				IQuery *q=db->Prepare("INSERT INTO extra_clients (hostname) SELECT ? AS hostname WHERE NOT EXISTS (SELECT hostname FROM extra_clients WHERE hostname=?)");
				q->Bind(hostname);
				q->Bind(hostname);
				q->Write();
				q->Reset();
			}
		}

		JSON::Array status;
		IDatabase *db=helper.getDatabase();
		std::string filter;
		if(!clientids.empty())
		{
			filter=" WHERE ";
			for(size_t i=0;i<clientids.size();++i)
			{
				filter+="id="+nconvert(clientids[i]);
				if(i+1<clientids.size())
					filter+=" OR ";
			}
		}
		db_results res=db->Read("SELECT id, name, strftime('"+helper.getTimeFormatString()+"', lastbackup, 'localtime') AS lastbackup, strftime('"+helper.getTimeFormatString()+"', lastseen, 'localtime') AS lastseen,"
			"strftime('"+helper.getTimeFormatString()+"', lastbackup_image, 'localtime') AS lastbackup_image FROM clients"+filter);

		int backup_ok_mod=3;
		db_results res_t=db->Read("SELECT value FROM settings WHERE key='backup_ok_mod' AND clientid=0");
		if(res_t.size()>0)
		{
			backup_ok_mod=watoi(res_t[0][L"value"]);
		}

		std::vector<SStatus> client_status=ServerStatus::getStatus();

		for(size_t i=0;i<res.size();++i)
		{
			JSON::Object stat;
			int clientid=watoi(res[i][L"id"]);
			std::wstring clientname=res[i][L"name"];
			stat.set("id", clientid);
			stat.set("name", clientname);
			stat.set("lastbackup", res[i][L"lastbackup"]);
			stat.set("lastseen", res[i][L"lastseen"]);
			stat.set("lastbackup_image", res[i][L"lastbackup_image"]);

			std::string ip="-";
			int i_status=0;
			bool online=false;
			for(size_t j=0;j<client_status.size();++j)
			{
				if(client_status[j].client==wnarrow(clientname))
				{
					if(client_status[j].r_online==true)
					{
						online=true;
					}
					unsigned char *ips=(unsigned char*)&client_status[j].ip_addr;
					ip=nconvert(ips[0])+"."+nconvert(ips[1])+"."+nconvert(ips[2])+"."+nconvert(ips[3]);

					if(!client_status[j].wrong_ident)
						i_status=client_status[j].statusaction;
					else if(client_status[i].too_many_clients)
						i_status=12;
					else
						i_status=11;
				}
			}

			stat.set("online", online);
			stat.set("ip", ip);
			stat.set("status", i_status);

			ServerSettings settings(db, clientid);
			IQuery *q=db->Prepare("SELECT id FROM clients WHERE lastbackup IS NOT NULL AND datetime('now','-"+nconvert(settings.getSettings()->update_freq_incr*backup_ok_mod)+" seconds')<lastbackup AND id=?");
			q->Bind(clientid);
			db_results res_file_ok=q->Read();
			q->Reset();
			stat.set("file_ok", !res_file_ok.empty());

			q=db->Prepare("SELECT id FROM clients WHERE lastbackup_image IS NOT NULL AND datetime('now','-"+nconvert(settings.getSettings()->update_freq_image_incr*backup_ok_mod)+" seconds')<lastbackup_image AND id=?");
			q->Bind(clientid);
			res_file_ok=q->Read();
			q->Reset();
			stat.set("image_ok", !res_file_ok.empty());

			status.add(stat);
		}

		if(rights=="all")
		{
			for(size_t i=0;i<client_status.size();++i)
			{
				bool found=false;
				for(size_t j=0;j<res.size();++j)
				{
					if(wnarrow(res[j][L"name"])==client_status[i].client)
					{
						found=true;
						break;
					}
				}

				if(found) continue;

				JSON::Object stat;
				stat.set("id", (std::string)"-");
				stat.set("name", client_status[i].client);
				stat.set("lastbackup", (std::string)"-");
				stat.set("lastseen", (std::string)"-");
				stat.set("lastbackup_image", (std::string)"-");
				stat.set("online", client_status[i].r_online);
				std::string ip;
				unsigned char *ips=(unsigned char*)&client_status[i].ip_addr;
				ip=nconvert(ips[0])+"."+nconvert(ips[1])+"."+nconvert(ips[2])+"."+nconvert(ips[3]);
				stat.set("ip", ip);

				if(client_status[i].wrong_ident)
					stat.set("status", 11);
				else if(client_status[i].too_many_clients)
					stat.set("status", 12);
				else
					stat.set("status", 10);

				stat.set("file_ok", false);
				stat.set("image_ok", false);
				stat.set("rejected", true);

				status.add(stat);
			}
		}
		JSON::Array extra_clients;

		if(rights=="all")
		{
			res=db->Read("SELECT id, hostname, lastip FROM extra_clients");
			for(size_t i=0;i<res.size();++i)
			{
				JSON::Object extra_client;

				extra_client.set("hostname", res[i][L"hostname"]);

				_i64 i_ip=os_atoi64(wnarrow(res[i][L"lastip"]));

				bool online=false;

				for(size_t j=0;j<client_status.size();++j)
				{
					if(i_ip==(_i64)client_status[j].ip_addr)
					{
						online=true;
					}
				}
				extra_client.set("id", res[i][L"id"]);
				extra_client.set("online", online);

				extra_clients.add(extra_client);
			}
			ret.set("allow_extra_clients", true);
		}

		ret.set("status", status);
		ret.set("extra_clients", extra_clients);

	}
	else
	{
		ret.set("error", 1);
	}
	helper.Write(ret.get(false));
}

#endif //CLIENT_ONLY<commit_msg>Fixed detailed client status<commit_after>/*************************************************************************
*    UrBackup - Client/Server backup system
*    Copyright (C) 2011  Martin Raiber
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

#ifndef CLIENT_ONLY

#include "action_header.h"
#include "../server_settings.h"
#include "../os_functions.h"
#include "../server_status.h"

ACTION_IMPL(status)
{
	Helper helper(tid, &GET, &PARAMS);
	JSON::Object ret;

	std::string rights=helper.getRights("status");
	std::vector<int> clientids;
	IDatabase *db=helper.getDatabase();
	if(rights!="all" && rights!="none" )
	{
		std::vector<std::string> s_clientid;
		Tokenize(rights, s_clientid, ",");
		for(size_t i=0;i<s_clientid.size();++i)
		{
			clientids.push_back(atoi(s_clientid[i].c_str()));
		}
	}

	SUser *session=helper.getSession();
	if(session!=NULL && session->id==-1) return;
	if(session!=NULL && (rights=="all" || !clientids.empty()) )
	{
		{
			ServerSettings settings(db);
			if(!os_directory_exists(settings.getSettings()->backupfolder) || !os_directory_exists(settings.getSettings()->backupfolder_uncompr) || settings.getSettings()->backupfolder.empty())
			{
				ret.set("dir_error", true);
			}
			else if(!os_directory_exists(settings.getSettings()->backupfolder+os_file_sep()+L"clients") && !os_create_dir(settings.getSettings()->backupfolder+os_file_sep()+L"clients") )
			{
				ret.set("dir_error" ,true);
			}
		}

		bool details=false;
		if(GET.find(L"details")!=GET.end())
		{
			details=true;
			ret.set("details", true);
		}
		std::wstring hostname=GET[L"hostname"];
		if(!hostname.empty())
		{
			if(GET[L"remove"]==L"true")
			{
				IQuery *q=db->Prepare("DELETE FROM extra_clients WHERE id=?");
				q->Bind(hostname);
				q->Write();
				q->Reset();
			}
			else
			{
				IQuery *q=db->Prepare("INSERT INTO extra_clients (hostname) SELECT ? AS hostname WHERE NOT EXISTS (SELECT hostname FROM extra_clients WHERE hostname=?)");
				q->Bind(hostname);
				q->Bind(hostname);
				q->Write();
				q->Reset();
			}
		}

		JSON::Array status;
		IDatabase *db=helper.getDatabase();
		std::string filter;
		if(!clientids.empty())
		{
			filter=" WHERE ";
			for(size_t i=0;i<clientids.size();++i)
			{
				filter+="id="+nconvert(clientids[i]);
				if(i+1<clientids.size())
					filter+=" OR ";
			}
		}
		db_results res=db->Read("SELECT id, name, strftime('"+helper.getTimeFormatString()+"', lastbackup, 'localtime') AS lastbackup, strftime('"+helper.getTimeFormatString()+"', lastseen, 'localtime') AS lastseen,"
			"strftime('"+helper.getTimeFormatString()+"', lastbackup_image, 'localtime') AS lastbackup_image FROM clients"+filter);

		int backup_ok_mod=3;
		db_results res_t=db->Read("SELECT value FROM settings WHERE key='backup_ok_mod' AND clientid=0");
		if(res_t.size()>0)
		{
			backup_ok_mod=watoi(res_t[0][L"value"]);
		}

		std::vector<SStatus> client_status=ServerStatus::getStatus();

		for(size_t i=0;i<res.size();++i)
		{
			JSON::Object stat;
			int clientid=watoi(res[i][L"id"]);
			std::wstring clientname=res[i][L"name"];
			stat.set("id", clientid);
			stat.set("name", clientname);
			stat.set("lastbackup", res[i][L"lastbackup"]);
			stat.set("lastseen", res[i][L"lastseen"]);
			stat.set("lastbackup_image", res[i][L"lastbackup_image"]);

			std::string ip="-";
			int i_status=0;
			bool online=false;
			for(size_t j=0;j<client_status.size();++j)
			{
				if(client_status[j].client==wnarrow(clientname))
				{
					if(client_status[j].r_online==true)
					{
						online=true;
					}
					unsigned char *ips=(unsigned char*)&client_status[j].ip_addr;
					ip=nconvert(ips[0])+"."+nconvert(ips[1])+"."+nconvert(ips[2])+"."+nconvert(ips[3]);

					if(client_status[j].wrong_ident)
						i_status=11;
					else if(client_status[i].too_many_clients)
						i_status=12;
					else
						i_status=client_status[j].statusaction;
				}
			}

			stat.set("online", online);
			stat.set("ip", ip);
			stat.set("status", i_status);

			ServerSettings settings(db, clientid);
			IQuery *q=db->Prepare("SELECT id FROM clients WHERE lastbackup IS NOT NULL AND datetime('now','-"+nconvert(settings.getSettings()->update_freq_incr*backup_ok_mod)+" seconds')<lastbackup AND id=?");
			q->Bind(clientid);
			db_results res_file_ok=q->Read();
			q->Reset();
			stat.set("file_ok", !res_file_ok.empty());

			q=db->Prepare("SELECT id FROM clients WHERE lastbackup_image IS NOT NULL AND datetime('now','-"+nconvert(settings.getSettings()->update_freq_image_incr*backup_ok_mod)+" seconds')<lastbackup_image AND id=?");
			q->Bind(clientid);
			res_file_ok=q->Read();
			q->Reset();
			stat.set("image_ok", !res_file_ok.empty());

			status.add(stat);
		}

		if(rights=="all")
		{
			for(size_t i=0;i<client_status.size();++i)
			{
				bool found=false;
				for(size_t j=0;j<res.size();++j)
				{
					if(wnarrow(res[j][L"name"])==client_status[i].client)
					{
						found=true;
						break;
					}
				}

				if(found) continue;

				JSON::Object stat;
				stat.set("id", (std::string)"-");
				stat.set("name", client_status[i].client);
				stat.set("lastbackup", (std::string)"-");
				stat.set("lastseen", (std::string)"-");
				stat.set("lastbackup_image", (std::string)"-");
				stat.set("online", client_status[i].r_online);
				std::string ip;
				unsigned char *ips=(unsigned char*)&client_status[i].ip_addr;
				ip=nconvert(ips[0])+"."+nconvert(ips[1])+"."+nconvert(ips[2])+"."+nconvert(ips[3]);
				stat.set("ip", ip);

				if(client_status[i].wrong_ident)
					stat.set("status", 11);
				else if(client_status[i].too_many_clients)
					stat.set("status", 12);
				else
					stat.set("status", 10);

				stat.set("file_ok", false);
				stat.set("image_ok", false);
				stat.set("rejected", true);

				status.add(stat);
			}
		}
		JSON::Array extra_clients;

		if(rights=="all")
		{
			res=db->Read("SELECT id, hostname, lastip FROM extra_clients");
			for(size_t i=0;i<res.size();++i)
			{
				JSON::Object extra_client;

				extra_client.set("hostname", res[i][L"hostname"]);

				_i64 i_ip=os_atoi64(wnarrow(res[i][L"lastip"]));

				bool online=false;

				for(size_t j=0;j<client_status.size();++j)
				{
					if(i_ip==(_i64)client_status[j].ip_addr)
					{
						online=true;
					}
				}
				extra_client.set("id", res[i][L"id"]);
				extra_client.set("online", online);

				extra_clients.add(extra_client);
			}
			ret.set("allow_extra_clients", true);
		}

		ret.set("status", status);
		ret.set("extra_clients", extra_clients);

	}
	else
	{
		ret.set("error", 1);
	}
	helper.Write(ret.get(false));
}

#endif //CLIENT_ONLY<|endoftext|>