#include "sync.h" #include #include #include #include "exaptions.h" #include #include #include #include "config.h" #ifdef QT_DEBUG #include #include #endif namespace syncLib{ Sync::Sync(const QString address, int port, const QString &datadir): node(nullptr), db(nullptr), player(nullptr), qyery(nullptr), buffer(nullptr), curentSong(nullptr) { node = new Node(address , this->port = port); player = new QMediaPlayer(nullptr,QMediaPlayer::LowLatency); buffer = new QBuffer; if(!player->isAvailable()){ throw MediaException(); } fbroadcaster = false; initDB(datadir); connect(node, SIGNAL(Message(ETcpSocket*)), SLOT(packageRender(ETcpSocket*))); connect(&deepScaner, SIGNAL(scaned(QList*)), SLOT(deepScaned(QList*))); connect(player, SIGNAL(positionChanged(qint64)), SIGNAL(seekChanged(qint64))); connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), SLOT(endPlay(QMediaPlayer::State))); } bool Sync::findHeader(const Song &song){ for(SongHeader & header: playList){ if(header == static_cast(song)){ curentSong = &header; return true; } } return false; } void Sync::sqlErrorLog(const QString &qyery){ #ifdef QT_DEBUG qDebug()<< qyery << ": fail:\n " <qyery->lastError(); #endif } void Sync::initDB(const QString &database){ if(db) return; dataBaseName = database; db = new QSqlDatabase(); *db = QSqlDatabase::addDatabase("QSQLITE", database); QDir d(QString("./%0").arg(dataBaseName)); db->setDatabaseName(d.absolutePath()); if(db->open()){ qyery = new QSqlQuery(*db); QString qyer = QString("CREATE TABLE IF NOT EXISTS %0" "(id INTEGER PRIMARY KEY AUTOINCREMENT, " "name VARCHAR(100), " "size INT NOT NULL, " "data BLOB NOT NULL)").arg(DATATABLE_NAME); if(!qyery->exec(qyer)){ sqlErrorLog(qyer); throw InitDBError(); delete db; return; } qyer = QString("CREATE UNIQUE INDEX IF NOT EXISTS i%0 ON %0(name,size)").arg(DATATABLE_NAME); if(!qyery->exec(qyer)){ sqlErrorLog(qyer); throw InitDBError(); delete db; return; } } updateAvailableSongs(); } int Sync::save(const Song &song){ QString qyer = QString("SELECT id from %0 where name='%1' and size=%2").arg(DATATABLE_NAME, song.name, QString::number(song.size)); if(!qyery->exec(qyer)){ sqlErrorLog(qyer); return -1; } if(qyery->next()){ return qyery->value(0).toInt(); } qyer = QString("INSERT INTO %0 (name,size,data) VALUES" "('%1',%2,:val)").arg(DATATABLE_NAME, song.name, QString::number(song.size)); if(!qyery->prepare(qyer)){ sqlErrorLog(qyer + " prepare error"); return -1; } qyery->bindValue(":val",song.source); if(!qyery->exec()){ sqlErrorLog(qyer); return -1; } if(!qyery->exec(QString("SELECT MAX(id) from %0").arg(DATATABLE_NAME))){ sqlErrorLog(qyer); return -1; } if(!qyery->next()) return -1; int result = qyery->value(0).toInt(); updateAvailableSongs(); return result; } bool Sync::updateAvailableSongs(){ QString qyer = QString("SELECT id,name,size from %0").arg(DATATABLE_NAME); if(!qyery->exec(qyer)){ sqlErrorLog(qyer); return false; } playList.clear(); while(qyery->next()){ SongHeader song; song.id = qyery->value(0).toInt(); song.name = qyery->value(1).toString(); song.size = qyery->value(2).toInt(); playList.push_back(song); } return true; } bool Sync::load(const SongHeader &song,Song &result){ result.clear(); if(song.id > -1){ QString qyer = QString("SELECT * from %0 where id=%1").arg(DATATABLE_NAME).arg(song.id); if(!qyery->exec(qyer)){ return false; } }else if(!song.name.isEmpty() && song.size > 0){ QString qyer = QString("SELECT * from %0 where name='%1' and size=%2").arg(DATATABLE_NAME).arg(song.name).arg(song.size); if(!qyery->exec(qyer)){ return false; } }else { return false; } if(!qyery->next()){ return false; } result.id = qyery->value(0).toInt(); result.name = qyery->value(1).toString(); result.size = qyery->value(2).toInt(); result.source = qyery->value(3).toByteArray(); return true; } bool Sync::play(const SongHeader &header, const Syncer *syncdata){ if(!header.isValid()){ return false; } QString qyer = QString("SELECT * from %0 where name='%1' and size=%2").arg(DATATABLE_NAME).arg(header.name).arg(header.size); if(!qyery->exec(qyer)){ sqlErrorLog(qyer); return false; } if(!qyery->next()){ return false; } Song song; song.id = qyery->value(0).toInt(); song.name = qyery->value(1).toString(); song.size = qyery->value(2).toInt(); song.source = qyery->value(3).toByteArray(); return Sync::play(song, syncdata); } bool Sync::play(const Song &song, const Syncer *syncdata){ if(!song.isValid()){ return false; } buffer->close(); buffer->setData(song.source); buffer->open(QIODevice::ReadOnly); player->setMedia(QMediaContent(), buffer); fbroadcaster = !bool(syncdata); if(!findHeader(song) && save(song) > -1 && !findHeader(song)){ return false; } if(fbroadcaster){ package pac; if(!createPackage(t_song_h | t_sync, pac)){ throw CreatePackageExaption(); } node->WriteAll(pac.parseTo()); } if(syncdata && !sync(*syncdata)){ return false; } player->play(); return true; } bool Sync::play(int id_song, Syncer *syncdata){ QString qyer = QString("SELECT * from %0 where id=%1").arg(DATATABLE_NAME).arg(id_song); if(!qyery->exec(qyer) || !qyery->next()){ return false; } Song song; song.id = qyery->value(0).toInt(); song.name = qyery->value(1).toString(); song.size = qyery->value(2).toInt(); song.source = qyery->value(3).toByteArray(); return Sync::play(song ,syncdata); } bool Sync::play(QString url){ int id = addNewSong(url); if(id < 0){ return false; } return Sync::play(id); } void Sync::pause(bool state){ if(state){ player->pause(); }else{ player->play(); } } void Sync::stop(){ buffer->close(); player->stop(); } void Sync::jump(const qint64 seek){ player->setPosition(seek); } bool Sync::sync(const Syncer &sync){ milliseconds sync_time = sync.run - ChronoTime::now(); if(sync_time > MAX_SYNC_TIME && sync_time <= 0) return false; Clock run_time = ChronoTime::from(sync.run); do { std::this_thread::yield(); } while (std::chrono::high_resolution_clock::now() < run_time); player->setPosition(sync.seek); return true; } bool Sync::addNode(const QString ip, int port){ if(!node->addNode(ip, port)) return false; rescan(); return true; } void Sync::scan(){ rescan(true); } const QList& Sync::getServersList() const{ return servers; } bool Sync::listen(ETcpSocket *server){ if(!server){ return false; } if(!server->getSource()->isOpen() && server->getSource()->open(QIODevice::ReadWrite)){ return false; } package pac; if(!createPackage(t_sync,pac)){ return false; } return server->Write(pac.parseTo()); } bool Sync::createPackage(Type type, package &pac){ pac.clear(); pac.type = type; if(type & TypePackage::t_sync && fbroadcaster){ pac.playdata.run = ChronoTime::now() + SYNC_TIME; pac.playdata.seek = player->position() + SYNC_TIME; } if( type & TypePackage::t_feedback && !fbroadcaster){ pac.playdata.run = ChronoTime::now(); pac.playdata.seek = player->position(); } if(type & TypePackage::t_song_h && fbroadcaster){ if(!curentSong) return false; pac.header = *curentSong; } if(type & TypePackage::t_song && fbroadcaster){ if(!curentSong) return false; if(!load(*curentSong, pac.source)) return false; } if(fbroadcaster) pac.type = TypePackage(pac.type | t_brodcaster); return pac.isValid(); } void Sync::packageRender(ETcpSocket *socket){ QByteArray *array; while((array = socket->topStack())){ package pkg; if(!pkg.parseFrom((*array))){ throw BadAnswerExaption(); continue; } // package answer; // scaning servers if(pkg.getType() & t_brodcaster && servers.indexOf(socket) == -1){ servers.append(socket); emit networkStateChange(); } if(!(pkg.getType() & t_brodcaster) && servers.indexOf(socket) != -1){ servers.removeOne(socket); emit networkStateChange(); } if(fbroadcaster == (pkg.getType() & t_brodcaster)){ throw BrodcastConflict(); return; } if(pkg.getType() & t_brodcaster){ // if requst from server if(pkg.getType() & t_sync){ if(!play(pkg.getHeader(), &pkg.getPlayData()) && !play(pkg.getSong(), &pkg.getPlayData())){ Type requestType = t_song_h; if(pkg.getType() & t_song_h) requestType = t_song; package answer; if(!createPackage(requestType | t_sync, answer)){ throw CreatePackageExaption(); } socket->Write(answer.parseTo()); }else{ package feedback; if(!createPackage(t_feedback, feedback)){ throw feedbackError(); } socket->Write(feedback.parseTo()); } } if(pkg.getType() & t_close){ socket->getSource()->close(); node->getClients()->removeOne(socket); delete socket; } if(pkg.getType() & t_what){ package answer; if(!createPackage(t_void, answer)){ throw CreatePackageExaption(); } socket->Write(answer.parseTo()); } }else{ if(pkg.getType() & t_sync ){ if(!curentSong){ return ; } } Type responceType = pkg.getType(); if(pkg.getType() & t_feedback){ if(!curentSong){ return ; } unsigned int diff = ChronoTime::abs(player->position() - (pkg.getPlayData().seek + (ChronoTime::now() - pkg.getPlayData().run))); #ifdef QT_DEBUG qDebug() << "diff " << socket->name() <<": " << diff; #endif if(diff > 1){ responceType = responceType | t_sync; }else{ std::cout<<"Synced!!"<Write(answer.parseTo()); if(pkg.getType() & t_close){ socket->getSource()->close(); node->getClients()->removeOne(socket); delete socket; } } socket->nextItem(); } } void Sync::rescan(bool deep){ package pac; if(!createPackage(t_what,pac)){ throw CreatePackageExaption(); return; } node->WriteAll(pac.parseTo()); if(deep){ deepScaner.setInterval(DEEP_SCANER_INTERVAL); deepScaner.scane(port); } } void Sync::deepScaned(QList * list){ package pac; if(!createPackage(t_what,pac)){ throw CreatePackageExaption(); return; } QByteArray array = pac.parseTo(); for(ETcpSocket * i: *list){ node->addNode(i); i->Write(array); } } void Sync::endPlay(QMediaPlayer::State state){ if(state == QMediaPlayer::StoppedState){ curentSong = nullptr; fbroadcaster = false; } } QString Sync::getVersion(){ return QString(tr("Version") + "%0.%1.%2").arg(MAJOR_VERSION).arg(MINOR_VERSION).arg(REVISION_VERSION); } bool Sync::setValume(unsigned int valume){ if(valume > 100) return false; player->setVolume(valume); return true; } unsigned int Sync::getValume() const{ return player->volume(); } unsigned int Sync::seek() const{ return player->position(); } const QList* Sync::getPlayList() const{ return &playList; } const SongHeader* Sync::getCurentSong() const{ return curentSong; } int Sync::addNewSong(const QString &url){ QFile f(url); if(!f.open(QIODevice::ReadOnly)){ return -1; } QByteArray bytes = f.readAll(); f.close(); QString name = url.right(url.lastIndexOf(QRegularExpression("[\\/]"))); // meby [[\\\/]] Song song; song.name = name; song.size = bytes.size(); song.source = bytes; return Sync::save(song); } qint64 Sync::getEndPoint() const { return player->duration(); } Sync::~Sync(){ delete node; delete db; delete player; servers.clear(); QSqlDatabase::removeDatabase(dataBaseName); } }