diff --git a/sync/ETcpSocket.cpp b/sync/ETcpSocket.cpp new file mode 100755 index 0000000..c78b20a --- /dev/null +++ b/sync/ETcpSocket.cpp @@ -0,0 +1,144 @@ +#include "ETcpSocket.h" +#include "exaptions.h" + +ETcpSocket::ETcpSocket() +{ + source=new QTcpSocket(); + init(); +} +ETcpSocket::ETcpSocket(QTcpSocket*ptr) +{ + source=ptr; + init(); +} + +ETcpSocket::ETcpSocket(const QString& address, int port){ + source = new QTcpSocket(); + if(!source->bind(QHostAddress(address),port) || !source->open(QIODevice::ReadWrite)){ + throw AddNodeExaption(); + } + init(); +} + +void ETcpSocket::init(){ + array=new QByteArray; + connect(source,SIGNAL(connected()),this,SLOT(connected_())); + connect(source,SIGNAL(disconnected()),this,SLOT(disconnected_())); + connect(source,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(error_(QAbstractSocket::SocketError))); + connect(source,SIGNAL(hostFound()),this,SLOT(hostFound_())); + connect(source,SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),this,SLOT(proxyAuthenticationRequired_(const QNetworkProxy &, QAuthenticator *))); + connect(source,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(stateChanged_(QAbstractSocket::SocketState))); + connect(source,SIGNAL(readyRead()),this,SLOT(readReady_())); +} + +void ETcpSocket::error_(QAbstractSocket::SocketError i){ + emit Error(this,i); +} + +void ETcpSocket::connected_(){ + emit Connected(this); +} + +void ETcpSocket::disconnected_(){ + emit Disconnected(this); +} + +void ETcpSocket::hostFound_(){ + emit HostFound(this); +} + +void ETcpSocket::proxyAuthenticationRequired_(const QNetworkProxy &proxy, QAuthenticator *authenticator){ + emit ProxyAuthenticationRequired(this,proxy,authenticator); +} + +void ETcpSocket::stateChanged_(QAbstractSocket::SocketState socketState){ + emit StateChanged(this,socketState); +} + +void ETcpSocket::readReady_(){ + bool sizewrite=array->isEmpty(); + //while(source->bytesAvailable()) + array->append(source->readAll()); + QDataStream stream(array,QIODevice::ReadOnly); + if(sizewrite) + stream>>size; +#ifdef QT_DEBUG + qDebug()<<"messae size:"<size(); +#endif + if(size==array->size()) + { + array->remove(0,sizeof(qint32)); + ReadyStack.push_back(array); + array=new QByteArray(); + emit Message(this); + }else{ + emit donwload(array->size(),size); + } + // emit ReadReady(this); +} + +QString ETcpSocket::name() const{ + return source->peerAddress().toString(); +} + +QByteArray* ETcpSocket::topStack(){ + if(ReadyStack.size()) + return ReadyStack.front(); + return NULL; +} + +QTcpSocket* ETcpSocket::getSource()const{ + return source; +} + +void ETcpSocket::nextItem(){ + if( ReadyStack.size()){ + ReadyStack.pop_front(); + } +} + +int ETcpSocket::sizeDescriptPackege(){ + return sizeof(qint32); +} + +QString ETcpSocket::toStringTcp(){ + return source->peerAddress().toString(); +} + +bool ETcpSocket::Write(const QByteArray&data){ + if(source->state()==QTcpSocket::ConnectedState){ + QByteArray array; + QDataStream stream(&array,QIODevice::ReadWrite); + stream<seek(0); + stream<write(array); + qDebug()<<"size write:"<write(array)==(array.size()); +#endif + } + return false; +} + +ETcpSocket::~ETcpSocket() +{ + for(QByteArray*i:ReadyStack){ + i->clear(); + delete i; + } + disconnect(source,SIGNAL(connected()),this,SLOT(connected_())); + disconnect(source,SIGNAL(disconnected()),this,SLOT(disconnected_())); + disconnect(source,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(error_(QAbstractSocket::SocketError))); + disconnect(source,SIGNAL(hostFound()),this,SLOT(hostFound_())); + disconnect(source,SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),this,SLOT(proxyAuthenticationRequired_(const QNetworkProxy &, QAuthenticator *))); + disconnect(source,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(stateChanged_(QAbstractSocket::SocketState))); + disconnect(source,SIGNAL(readyRead()),this,SLOT(readReady_())); + source->deleteLater(); +} diff --git a/sync/ETcpSocket.h b/sync/ETcpSocket.h new file mode 100755 index 0000000..c87d518 --- /dev/null +++ b/sync/ETcpSocket.h @@ -0,0 +1,74 @@ +#ifndef CLIENT_H +#define CLIENT_H +#include +#include +#include +#include + +/** + * @brief The ETcpSocket class + * example : + * ETcpSocket *tcp; + * try{ + * tcp = new ETcpSocket(addres,port); + * }catch(addNodeExaption e){ + * e.what(); + * } + * QByteArray *array; + * while(array = tcp.getSource()){ + * package pkg(*array); + * package ans = ansver(pkg); + * tcp.Write(ans); + * array->clear(); + * delete array; + * + * } + * + */ +class ETcpSocket:public QObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ name) +private: + QTcpSocket *source; + QByteArray *array; + qint32 size; + QList ReadyStack; + void init(); + +private slots: + void connected_(); + void disconnected_(); + void error_(QAbstractSocket::SocketError socketError); + void hostFound_(); + void readReady_(); + void proxyAuthenticationRequired_(const QNetworkProxy &proxy, QAuthenticator *authenticator); + void stateChanged_(QAbstractSocket::SocketState socketState); +public: + explicit ETcpSocket(); + explicit ETcpSocket(QTcpSocket*); + explicit ETcpSocket(const QString& addres,int port); + QTcpSocket* getSource()const; + QByteArray* topStack(); + void nextItem(); + int sizeDescriptPackege(); + bool Write(const QByteArray&); + ~ETcpSocket(); +public slots: + QString name()const; + QString toStringTcp(); +signals: + void donwload(int val,int max); + void ReadyComplit(ETcpSocket*,QDataStream&); + void Connected(ETcpSocket*); + void Message(ETcpSocket*); + void Disconnected(ETcpSocket*); + void Error(ETcpSocket*,QAbstractSocket::SocketError socketError); + void HostFound(ETcpSocket*); + void ProxyAuthenticationRequired(ETcpSocket*,const QNetworkProxy &proxy, QAuthenticator *authenticator); + void StateChanged(ETcpSocket*,QAbstractSocket::SocketState socketState); + //void Connected(QTcpSocket*); + //void errorConnect(QTcpSocket*,QAbstractSocket::SocketError); +}; + +#endif // CLIENT_H diff --git a/sync/LocalScanner.cpp b/sync/LocalScanner.cpp new file mode 100755 index 0000000..9dc76fb --- /dev/null +++ b/sync/LocalScanner.cpp @@ -0,0 +1,68 @@ +#include "LocalScanner.h" +#include "config.h" + +LocalScanner::LocalScanner(): + QObject() +{ + wiat.setInterval(1000); + connect(&wiat,SIGNAL(timeout()),SLOT(scaned_())); +} + +void LocalScanner::clear(){ + results.clear(); +} + +void LocalScanner::setInterval(int msec){ + wiat.setInterval(msec); +} + +void LocalScanner::clearSocets(){ + for(ETcpSocket* i:socets) + delete i; + socets.clear(); +} + +QHostAddress LocalScanner::thisAdress(){ + QList adress= QNetworkInterface::allAddresses(); + for(QHostAddress &ip:adress) + if(ip.protocol() == QAbstractSocket::IPv4Protocol && ip != QHostAddress(QHostAddress::LocalHost)) + return ip; + return QHostAddress::LocalHost; +} + +void LocalScanner::scane(){ + if(!socets.empty()) + return ; + QList adress= QNetworkInterface::allAddresses(); + clear(); + wiat.start(); + for(QHostAddress &ip:adress){ + if(ip.protocol() == QAbstractSocket::IPv4Protocol && ip != QHostAddress(QHostAddress::LocalHost)){ + for(int i=0;i<256;i++){ + QString adr= ip.toString(); + adr=adr.left(adr.lastIndexOf("."))+"."+QString::number(i); + ETcpSocket *temp=new ETcpSocket; + connect(temp,SIGNAL(Connected(ETcpSocket*)),SLOT(connected(ETcpSocket*))); + temp->getSource()->connectToHost(adr,DEDAULT_PORT); + socets.push_back(temp); + } + } + } +} + +void LocalScanner::scaned_(){ + wiat.stop(); + emit scaned(&results); + clearSocets(); +} + +void LocalScanner::connected(ETcpSocket *c){ + results.push_back(c); +} + +LocalScanner::~LocalScanner(){ + for(ETcpSocket* i: socets) + delete i; + socets.clear(); + +} diff --git a/sync/LocalScanner.h b/sync/LocalScanner.h new file mode 100755 index 0000000..0db7ada --- /dev/null +++ b/sync/LocalScanner.h @@ -0,0 +1,29 @@ +#ifndef LocalServers_H +#define LocalServers_H +#include +#include +#include +#include +#include "ETcpSocket.h" + +class LocalScanner:public QObject{ + Q_OBJECT +private: + QList socets; + QList results; + QTimer wiat; + void clear(); + void clearSocets(); +private slots: + void scaned_(); + void connected(ETcpSocket *); +public: + void scane(); + void setInterval(int msec); + static QHostAddress thisAdress(); + LocalScanner(); + ~LocalScanner(); +signals: + void scaned(QList*); +}; +#endif // LocalServers_H diff --git a/sync/config.h b/sync/config.h new file mode 100644 index 0000000..5ae3218 --- /dev/null +++ b/sync/config.h @@ -0,0 +1,19 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// LIB VERSION +#define MAJOR_VERSION 0 +#define MINOR_VERSION 0 +#define REVISION_VERSION 0 + +// sqlite config +#define DATABASE_NAME "songdata.dat" +#define DATATABLE_NAME "songs" + +// network config +#define DEDAULT_PORT 1239 +#define MAX_SYNC_TIME 20 * 1000 // 10 sec on microsec +#define SYNC_TIME 5 * 1000 // 5 sec on microsec +#define DEEP_SCANER_INTERVAL 10000 // 10 sec + +#endif // CONFIG_H diff --git a/sync/exaptions.h b/sync/exaptions.h new file mode 100644 index 0000000..54a9126 --- /dev/null +++ b/sync/exaptions.h @@ -0,0 +1,58 @@ +#ifndef EXAPTIONS_H +#define EXAPTIONS_H + +#include +#include +#include +/** + * @brief The MediaException class + */ +class MediaException:public std::exception +{ +public: + QString what(){ + return QObject::tr("Your operating system or platform has not supported media files."); + } +}; + +class AddNodeExaption:public std::exception +{ +public: + QString what(){ + return QObject::tr("Address not available"); + } +}; + +class CreatePackageExaption:public std::exception +{ +public: + QString what(){ + return QObject::tr("Сould not generate network packet"); + } +}; + +class BadAnswerExaption:public std::exception +{ +public: + QString what(){ + return QObject::tr("could not parse message nodes."); + } +}; + +class BrodcastConflict:public std::exception +{ +public: + QString what(){ + return QObject::tr("The server received the packet from the server."); + } +}; + +class SyncError:public std::exception +{ +public: + QString what(){ + return QObject::tr("The playlist is empty, the player has nothing to play."); + } +}; + +#endif // EXAPTIONS_H diff --git a/sync/node.cpp b/sync/node.cpp new file mode 100644 index 0000000..5b09839 --- /dev/null +++ b/sync/node.cpp @@ -0,0 +1,210 @@ +#include "node.h" +#include +#include "song.h" +#include +#include "exaptions.h" +namespace syncLib{ + +package::package(){ + clear(); +} +package::package(const QByteArray &array): + package::package(){ + parseFrom(array); +} + +const SongHeader& package::getHeader() const{ + return header; +} + +const Song& package::getSong() const{ + return source; +} + +const Syncer& package::getPlayData() const{ + return playdata; +} + +const Type& package::getType() const{ + return type; +} + +bool package::isValid() const{ + + bool ret = true; + if(type == TypePackage::t_void){ + return false; + + } + + if(type & TypePackage::t_play){ + ret = ret && true; + + } + + if(type & TypePackage::t_sync){ + ret = ret && (playdata.run > 0 && playdata.seek > 0); + + } + + if(type & TypePackage::t_song_h){ + ret = ret && header.size > 0; + + } + + if(type & TypePackage::t_song){ + ret = ret && source.size > 0; + + } + + if(type & TypePackage::t_close){ + ret = ret && true; + + } + + if(type & TypePackage::t_stop){ + ret = ret && true; + + } + + return ret; + +} + +void package::clear(){ + type = TypePackage::t_void; + source.clear(); + playdata.run = 0; + playdata.seek = 0; +} + +QByteArray package::parseTo(){ + QByteArray temp; + QDataStream stream(temp); + temp.clear(); + if(isValid()){ + stream << static_cast(type); + + if(type & TypePackage::t_sync){ + stream << playdata.run; + stream << playdata.seek; + + } + + if(type & TypePackage::t_song_h){ + stream << header; + + } + + if(type & TypePackage::t_song){ + stream << source; + + } + + } + return temp; +} + +bool package::parseFrom(const QByteArray &array){ + type = TypePackage::t_void; + QDataStream stream(array); + + unsigned char temp_type; + stream >> temp_type; + type = static_cast (temp_type); + + if(type & TypePackage::t_sync){ + stream >> playdata.run; + stream >> playdata.seek; + + } + + if(type & TypePackage::t_song_h){ + stream >> header; + + } + + if(type & TypePackage::t_song){ + stream >> source; + + } + + return isValid(); +} + +Node::Node():QTcpServer(){ + connect(this,SIGNAL(acceptError(QAbstractSocket::SocketError)),SLOT(acceptError_(QAbstractSocket::SocketError))); + connect(this,SIGNAL(newConnection()),SLOT(newConnection_())); +} + +void Node::acceptError_(ETcpSocket*c){ + c->getSource()->close(); + clients.removeOne(c); + emit ClientDisconnected(c); + delete c; +} + +QList* Node::getClients(){ + return &clients; +} + +void Node::newConnection_(){ + ETcpSocket *newClient=new ETcpSocket(nextPendingConnection()); + clients.push_back(newClient); + connect(newClient,SIGNAL(Disconnected(ETcpSocket*)),this,SLOT(acceptError_(ETcpSocket*))); + connect(newClient,SIGNAL(Message(ETcpSocket*)),this,SLOT(readData(ETcpSocket*))); + emit ClientConnected(newClient); +} + +void Node::readData(ETcpSocket *c){ + emit Message(c); +} + +void Node::WriteAll(const QByteArray &data){ + for(ETcpSocket*i:clients){ + i->getSource()->write(data); + } +} + +void Node::disconnectClient(ETcpSocket *c){ + c->getSource()->close(); + clients.removeOne(c); + delete c; +} + +bool Node::addNode(const QString &node,int port){ + ETcpSocket *temp; + + try{ + temp = new ETcpSocket(node,port); + }catch(AddNodeExaption &e){ +#ifdef QT_DEBUG + qDebug() << e.what(); +#endif + return false; + } + + clients.push_back(temp); + return true; + +} + +bool Node::addNode(ETcpSocket *node){ + if(node->getSource()->isOpen()){ + clients.append(node); + return true; + } + return false; +} + +Node::~Node(){ + for(ETcpSocket *i:clients){ + i->getSource()->close(); + delete i; + } + this->close(); +} + +} + + diff --git a/sync/node.h b/sync/node.h new file mode 100644 index 0000000..6bc2e4a --- /dev/null +++ b/sync/node.h @@ -0,0 +1,107 @@ +#ifndef NODE_H +#define NODE_H +#include +#include "ETcpSocket.h" +#include +class Syncer; +namespace syncLib { + +typedef unsigned char Type; + +/** + * @brief The TypePackage enum + * t_void - this package empty and not valid. + * t_close - the information about close channel. + * t_sync - the infomation about sync playning media file on network. + * t_song - the package with this type is necessary for translite media data on network. + * t_stop - the package with type 'stop' necessary for stoping playning media files. + */ +enum TypePackage{ + t_void = 0x00, + t_play = 0x01, + t_song_h = 0x02, + t_song = 0x04, + t_sync = 0x08, + t_close = 0x10, + t_stop = 0x20, + t_what = 0x40, + t_brodcaster = 0x80 +}; + +/** + * @brief The package class. Package for translite media data on network + * + * parse map: + * 1 byle - type + * 4 byte - size of data of package (it avelable if type is t_sync or t_song) + * data + */ +class package +{ + /*parse map */ + +/* + * 1 byle - type + * 4 byte - size of data of package (it avelable if type is t_sync or t_song) + * data +*/ + +private: + Type type; + Song source; + SongHeader header; + Syncer playdata; +public: + package(); + package(const QByteArray& array); + ~package(); + /** + * @brief getHeader + * @return Header of the song + */ + const SongHeader& getHeader() const; + /** + * @brief getSong + * @return Song + */ + const Song& getSong() const; + /** + * @brief getPlayTime + * @return time of playning media data + */ + const Syncer &getPlayData() const; + const Type& getType() const; + bool isValid() const; + void clear(); + QByteArray parseTo(); + bool parseFrom(const QByteArray& array); + friend class Sync; +}; + +class Node:public QTcpServer{ + Q_OBJECT +protected: + QList clients; +private slots: + void acceptError_(ETcpSocket*); + void newConnection_(); + void readData(ETcpSocket*_client); +public: + Node(); + void WriteAll(const QByteArray&); + void disconnectClient(ETcpSocket*); + QList* getClients(); + bool addNode(const QString &node, int port = DEDAULT_PORT); + bool addNode(ETcpSocket* node); + ~Node(); +signals: + void Error(QString); + void Message(ETcpSocket*); + void ClientDisconnected(ETcpSocket*); + void ClientConnected(ETcpSocket*); +}; + +} + + +#endif // NODE_H diff --git a/sync/song.cpp b/sync/song.cpp new file mode 100644 index 0000000..05ac05f --- /dev/null +++ b/sync/song.cpp @@ -0,0 +1,81 @@ +#include "song.h" + +namespace syncLib{ + +SongHeader::SongHeader() +{ + this->id = -1; + this->name = ""; + this->size = 0; +} + +SongHeader& SongHeader::operator =(const SongHeader& right){ + this->id = right.id; + this->name = right.name; + this->size = right.size; + return *this; +} + +bool SongHeader::operator ==(const SongHeader& right){ + return this->name == right.name && this->size == right.size; +} + +unsigned int SongHeader::getSize() const{ + QByteArray size; + QDataStream stream(size); + stream << id << name << this->size; + return size.size(); +} + +Song::Song(): + SongHeader() +{ + source.clear(); +} + +QDataStream& operator << (QDataStream& stream, const SongHeader& song){ + stream << song.id; + stream << song.name; + stream << song.size; + return stream; +} +QDataStream& operator >> (QDataStream& stream, SongHeader& song){ + stream >> song.id; + stream >> song.name; + stream >> song.size; + return stream; +} + +Song::Song(const SongHeader& from) + :Song::Song() +{ + this->id = from.id; + this->name = from.name; + this->size = from.size; +} + +void Song::clear(){ + source.clear(); +} + +Song::~Song(){ + source.clear(); +} + +unsigned int Song::getSize() const{ + return SongHeader::getSize() + source.size(); +} + +QDataStream& operator << (QDataStream& stream,const Song& song){ + stream << static_cast(song); + stream << song.source; + return stream; +} + +QDataStream& operator >> (QDataStream& stream, Song& song){ + stream >> static_cast(song); + stream >> song.source; + return stream; +} + +} diff --git a/sync/song.h b/sync/song.h new file mode 100644 index 0000000..a23a084 --- /dev/null +++ b/sync/song.h @@ -0,0 +1,66 @@ +#ifndef SONG_H +#define SONG_H +#include +#include +#include +#include +/** + * @brief Time_point on nanosecunds (uint64_t) + */ +typedef quint64 milliseconds; + +namespace syncLib { + +/** + * @brief The Syncer struct + * + */ +struct Syncer +{ + /** + * @brief seek - wher is play media file + */ + milliseconds seek; + /** + * @brief run when is play media file (int) + */ + milliseconds run; +}; + +/** + * @brief The SongHeader class sound header with media information + * (id,size and name) + */ +class SongHeader +{ +public: + int id; + QString name; + int size; + SongHeader(); + SongHeader& operator = (const SongHeader& right); + bool operator == (const SongHeader& right); + virtual unsigned int getSize()const; + virtual ~SongHeader(); + friend QDataStream& operator << (QDataStream& stream, const SongHeader& song); + friend QDataStream& operator >> (QDataStream& stream, SongHeader& song); +}; +/** + * @brief The Song class + * into this calss added mediadata of playable media file. + */ +class Song : public SongHeader{ +private: + QByteArray source; +public: + Song(); + Song(const SongHeader& from); + void clear(); + unsigned int getSize() const; + ~Song(); + friend QDataStream& operator << (QDataStream& stream, const Song& song); + friend QDataStream& operator >> (QDataStream& stream, Song& song); + friend class Sync; +}; +} +#endif // SONG_H diff --git a/sync/sync.cpp b/sync/sync.cpp new file mode 100644 index 0000000..16d674d --- /dev/null +++ b/sync/sync.cpp @@ -0,0 +1,350 @@ +#include "sync.h" +#include +#include +#include +#include +#include "exaptions.h" +#include "time.h" +#include "thread" + +#include "config.h" + +#ifdef QT_DEBUG +#include +#endif + +namespace syncLib{ + +Sync::Sync(){ + node = new Node(); + player = new QMediaPlayer(nullptr,QMediaPlayer::LowLatency); + if(!player->isAvailable()){ + throw MediaException(); + } + initDB(); + connect(node,SIGNAL(Message(ETcpSocket*)),SLOT(packageRender(ETcpSocket*))); + connect(&deepScaner,SIGNAL(scaned(QList*)),SLOT(deepScaned(QList*))); + +} + +void Sync::initDB(){ + if(db) return; + *db = QSqlDatabase::addDatabase("QSQLITE"); + QDir d(QString("./%0").arg(DATABASE_NAME)); + db->setDatabaseName(d.absolutePath()); + if(db->open()){ + qyery = new QSqlQuery(*db); + QString qyer = QString("CREATE TABLE IF NOT EXISTS %0 " + "id int NOT NULL AUTO_INCREMENT," + "name VARCHAR(100)," + "size INT NOT NULL," + "data BLOB NOT NULL").arg(DATATABLE_NAME); + qyery->exec(qyer); + } +} + +int Sync::save(const Song &song){ + QString qyer = QString("INSERT INTO %0 (name, size, data) VALUES" + "(%1,%2, :data)").arg(DATATABLE_NAME, + song.name, + QString::number(song.size)); + qyery->prepare(qyer); + qyery->bindValue(":data",song.source); + if(!qyery->exec()) + return -1; + if(qyery->exec(QString("SELECT MAAX(id) form %0").arg(DATATABLE_NAME))) + return -1; + return qyery->value(0).toInt(); +} + +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; + } + + 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; +} + +/* + * information about chrono + * https://stackoverflow.com/questions/31255486/c-how-do-i-convert-a-stdchronotime-point-to-long-and-back + */ + +milliseconds Sync::now(){ + auto tim = std::chrono::system_clock::now(); + auto mc = std::chrono::time_point_cast(tim); + auto epoh = mc.time_since_epoch(); +#ifdef QT_DEBUG + qDebug() << epoh.count(); +#endif + return epoh.count(); +} + +Clock Sync::from(const milliseconds& mc){ + std::chrono::milliseconds dur(mc); + return Clock(dur); +} + +bool Sync::play(const SongHeader &header, const Syncer *syncdata){ + 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)){ + 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(Song &song, Syncer *syncdata){ + QBuffer buffer(&song.source); + player->setMedia(QMediaContent(), &buffer); + if(syncdata && !sync(*syncdata)){ + return false; + } + + fbroadcaster = !bool(syncdata); + if(fbroadcaster){ + package pac; + if(!createPackage(t_song_h | t_sync, pac)){ + throw CreatePackageExaption(); + } + node->WriteAll(pac.parseTo()); + } + + player->play(); + playList->push_front(static_cast(song)); + 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)){ + 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){ + QFile f(url); + if(!f.open(QIODevice::ReadOnly)){ + return false; + } + 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; + song.id = Sync::save(song); + if(song.id < 0) + return false; + return Sync::play(song); +} + +void Sync::pause(){ + player->pause(); +} + +void Sync::stop(){ + player->stop(); +} + +void Sync::jump(const int seek){ + player->setPosition(seek); +} + +bool Sync::sync(const Syncer &sync){ + milliseconds sync_time = sync.run - now(); + if(sync_time > MAX_SYNC_TIME && sync_time <= 0) + return false; + Clock run_time = 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::createPackage(Type type, package &pac){ + pac.clear(); + + pac.type = type; + + if(type & TypePackage::t_sync && fbroadcaster){ + + pac.playdata.run = now() + SYNC_TIME; + pac.playdata.seek = player->position() + SYNC_TIME; + + } + + if(type & TypePackage::t_song_h && fbroadcaster){ + if(playList->isEmpty()) + return false; + + pac.header = playList->front(); + + } + + if(type & TypePackage::t_song && fbroadcaster){ + if(playList->isEmpty()) + return false; + + if(!load(playList->front(), 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); + } + + if(!(pkg.getType() & t_brodcaster) && servers.indexOf(socket) != -1){ + servers.removeOne(socket); + } + + if(fbroadcaster == (pkg.getType() & t_brodcaster)){ + throw BrodcastConflict(); + return; + } + + if(pkg.getType() & t_brodcaster){ + +// if requst from server + + if(pkg.getType() & t_play){ + player->play(); + } + + if((pkg.getType() & t_song_h) && !play(pkg.getHeader(), &pkg.getPlayData())){ + if((pkg.getType() & t_song) && !play(pkg.getSong(), &pkg.getPlayData())){ + package answer; + if(!createPackage(t_song | t_sync, answer)){ + throw CreatePackageExaption(); + } + socket->Write(answer.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()); + } + + if(pkg.getType() & t_sync){ + if(playList->empty()){ + throw SyncError(); + } + } + + }else{ + + package answer; + if(!createPackage(pkg.getType() | ~t_what | ~t_play | ~t_stop | ~t_brodcaster, answer)){ + throw CreatePackageExaption(); + } + socket->Write(answer.parseTo()); + + if(pkg.getType() & t_close){ + socket->getSource()->close(); + node->getClients()->removeOne(socket); + delete socket; + } + + } + + array->clear(); + delete array; + + } +} + +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(); + } +} + +void Sync::deepScaned(QList * list){ + package pac; + if(!createPackage(t_what,pac)){ + throw CreatePackageExaption(); + return; + } + QByteArray array = pac.parseTo(); + for(ETcpSocket * i: *list){ + i->Write(array); + } +} + +Sync::~Sync(){ + delete node; + delete db; + delete player; + servers.clear(); +} + +} + + diff --git a/sync/sync.h b/sync/sync.h new file mode 100644 index 0000000..d64967e --- /dev/null +++ b/sync/sync.h @@ -0,0 +1,143 @@ +#ifndef SYNC_H +#define SYNC_H +#include "song.h" +#include "node.h" +#include "LocalScanner.h" +#include +class QSqlDatabase; +class QMediaPlayer; +class QSqlQuery; +namespace syncLib { + +typedef std::chrono::time_point Clock; + +class Node; + +/** + * @brief The Sync class is main class of this library. + * the 'sync' has supported synced playning media files on network and saving media data into local database. + */ +class Sync : public QObject +{ + Q_OBJECT +private: + Node *node; + QSqlDatabase *db; + QMediaPlayer *player; + QList* playList; + QSqlQuery *qyery; + QList servers; + bool fbroadcaster; + LocalScanner deepScaner; + + + /** + * @brief rescan - search for existing servers + * result saved in servers + */ + void rescan(bool deep = false); + /** + * @brief initDB initialize local database of song + */ + void initDB(); + /** + * @brief load song of database; + * @brief song - + * @brief result - the resulting value; + * @return true if everything's done + */ + bool load(const SongHeader &song, Song &result); + /** + * @brief save media data into local database. + * @param song savining media data. + * @return id of song saved on local database. + */ + int save(const Song &song); + /** + * @brief fromDataBase return a song from local database by id. + * @param id of song saved in local database. + * @return song drom local database. + */ + Song fromDataBase(const int id); + /** + * @brief now - get now time on microsecunds + * @return - count of microsecunds + */ + milliseconds now(); + /** + * @brief from cast to chrono secunds + * @param mcrs microseconds of uint_64 + * @return microseconds of chrono + */ + Clock from(const milliseconds &mcrs); + /** + * @brief createPackage - Create a package that shows current state of the node + * @param type - Type of an answer + * @param pac - the resulting value + * @return true if everything's done + */ + bool createPackage(Type type ,package& pac); +private slots: + + /** + * @brief packageRender - the handler of all messages received. + * @param socket + */ + void packageRender(ETcpSocket* socket); + /** + * @brief deepScaned scaning in local network + */ + void deepScaned(QList *); + +public: + /** + * @brief Play song in this device, if device has not supported playning media data this method throw MediaExcrption. + * @param header of song + * @param syncdata data of synbced playning of media data. + * @return true if all done else false. + */ + bool play(const SongHeader &header, const Syncer *syncdata = nullptr); + /** + * @brief Play song in this device, if device has not supported playning media data this method throw MediaExcrption. + * @param song playning media data. + * @param syncdata data of synbced playning of media data. + * @return true if all done else false. + */ + bool play(Song &song, Syncer *syncdata = nullptr); + /** + * @brief Play song from local media file. + * @param url of local media file. + * @return true if all done else false. + */ + bool play(QString url); + /** + * @brief Play song from local database by id. + * @param id_song of song. + * @return true if all done else false. + */ + bool play(int id_song, Syncer* syncdata = nullptr); + /** + * @brief Pause playning song. + */ + void pause(); + /** + * @brief stop playning song. + */ + void stop(); + /** + * @brief jump - jump to new position of playning media data. + * @param seek - a new position of media data. + */ + void jump(const int seek); + /** + * @brief sync with server + * @param sync - data of sync + */ + bool sync(const Syncer& sync); + Sync(); + ~Sync(); +}; +} + + +#endif // SYNC_H diff --git a/sync/sync.pro b/sync/sync.pro new file mode 100644 index 0000000..a7e7377 --- /dev/null +++ b/sync/sync.pro @@ -0,0 +1,44 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-10-28T16:29:39 +# +#------------------------------------------------- + +QT += network multimedia sql + +QT -= gui + +TARGET = sync +TEMPLATE = lib +CONFIG += staticlib + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + sync.cpp \ + song.cpp \ + node.cpp \ + ETcpSocket.cpp \ + LocalScanner.cpp + +HEADERS += \ + sync.h \ + song.h \ + node.h \ + config.h \ + exaptions.h \ + ETcpSocket.h \ + LocalScanner.h +unix { + target.path = /usr/lib + INSTALLS += target +}