--- /dev/null
+#include this into your qmake project file to
+#use the ZIP library
+
+LIBS += -L$$PWD/lib -lAurora
+INCLUDEPATH += $$PWD/include/aurora
+CONFIG += link_prl
\ No newline at end of file
VERSION = 0.2.0
-SOURCES += src/aurora.cpp
+SOURCES += \
+ src/aurora.cpp \
+ src/subtask.cpp
-HEADERS += src/aurora.h
+HEADERS += \
+ src/aurora.h \
+ src/subtask.h
INCLUDEPATH += src
-DEPENDPATH += $$INCLUDEPATH
\ No newline at end of file
+DEPENDPATH += $$INCLUDEPATH
+
+DEFINES += AURORA_LIBRARY_BUILD=1
+LIBS += -Lgpg/lib -lgpgme-pthread -lassuan -lgpg-error
+INCLUDEPATH += gpg/include
+
+gcc {
+ QMAKE_CXXFLAGS += -std=gnu++11
+}
\ No newline at end of file
+#!/bin/bash
set -e
fmake(){
fmake tests
make
make install
+
+
+#clean up and optimization
+cd ..
+rm gpg/bin/*-config
+strip gpg/bin/* || true
+echo
+echo Done.
-// Copyright (C) 2012 by Konrad Rosenbaum <konrad@silmor.de>
+// Copyright (C) 2012-2013 by Konrad Rosenbaum <konrad@silmor.de>
// protected under the GNU LGPL version 3 or at your option any newer.
// See COPYING.LGPL file that comes with this distribution.
//
#include "aurora.h"
+#include "subtask.h"
#include <QDebug>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
#include <QSettings>
#include <QTimer>
-struct AuroraPrivate
+struct AuroraUpdater::AuroraPrivate
{
AuroraPrivate();
- Aurora::State state;
+ Aurora::State state=Aurora::Initializing;
QUrl url;
- int pollint;
- QTimer*polltmr;
+ QString indexfile;
+ int pollint=0;
+ QTimer*polltmr=nullptr;
+ QPointer<SubTask>task;
bool isBlocking()const{return state!=Aurora::Initializing && state!=Aurora::Polling;}
static const QString AuroraGroup("aurora_updater");
AuroraPrivate::AuroraPrivate()
- :state(Aurora::Initializing),pollint(0),polltmr(0)
{
QSettings cfg;
cfg.beginGroup(AuroraGroup);
url=cfg.value("baseurl").toUrl();
- pollint=cfg.value("pollintervall",0).toInt();
+ indexfile=cfg.value("indexfile","auroraindex.xml").toString();
+ pollint=cfg.value("pollinterval",0).toInt();
+ qDebug()<<"Aurora: Initializing Auto-Updater. URL:"<<url.toString()<<"Interval:"<<pollint<<"s";
}
-void Aurora::setBaseUrl(QUrl u)
+void Aurora::setBaseUrl(QUrl u,QString idx)
{
d->url=u;
+ d->indexfile=idx;
QSettings c;c.beginGroup(AuroraGroup);
c.setValue("baseurl",u);
+ c.setValue("indexfile",idx);
+ qDebug()<<"Aurora: Changing URL to"<<u.toString();
startPoll();
}
void Aurora::setPollIntervall(int seconds)
{
if(seconds<0)seconds=0;
+ qDebug()<<"Aurora: Changing Interval to"<<seconds<<"s";
startPoll(seconds);
}
d->polltmr=0;
if(!d->isBlocking())
d->state=Initializing;
+ qDebug()<<"Aurora: Stopping Timer.";
+ }
+ if(!d->url.isValid() || d->indexfile.isEmpty()){
+ qDebug()<<"Aurora: no valid URL and/or index file, cannot poll.";
+ return;
}
if(d->pollint>0){
d->polltmr=new QTimer(this);
connect(d->polltmr,SIGNAL(timeout()), this,SLOT(pollnow()));
- d->polltmr->start(d->pollint);
+ d->polltmr->start(d->pollint*1000);
if(!d->isBlocking())
d->state=Polling;
- }
+ qDebug()<<"Aurora: Starting Timer.";
+ QTimer::singleShot(5,this,SLOT(pollnow()));
+ }else
+ qDebug()<<"Aurora: polling is deactivated, not starting.";
}
bool Aurora::isReadyForDownload() const
void Aurora::pollnow()
{
if(d->isBlocking()){
- qDebug()<<"missing an update poll cycle, in blocking mode";
+ qDebug()<<"Aurora: missing an update poll cycle, still working on another one...";
+ return;
+ }
+ qDebug()<<"Aurora: Polling for updates...";
+ //start the web request
+ if(!d->task.isNull())d->task->deleteLater();
+ QUrl u2(d->url);
+ const QString p=d->url.path(QUrl::FullyEncoded);
+ u2.setPath(p+"/"+d->indexfile,QUrl::StrictMode);
+ d->task=new SubTask(u2);
+ u2.setPath(p+"/"+d->indexfile+".asc",QUrl::StrictMode);
+ d->task->add(u2);
+ connect(d->task,SIGNAL(finished()),this,SLOT(checkMetaFile()));
+ connect(d->task,SIGNAL(error()),this,SLOT(abortCycle()));
+}
+
+
+void Aurora::checkMetaFile()
+{
+ if(d->task.isNull()){
+ d->state=Polling;
return;
}
+ //TODO: verify signature
+ //extract info
+ qDebug()<<"Aurora: got Meta Data, not doing anything about it yet";
+ d->task->deleteLater();
+ d->state=Polling;
+}
+
+void Aurora::abortCycle()
+{
+ qDebug()<<"Aurora: Aborting current poll cycle.";
+ if(!d->task.isNull())d->task->deleteLater();
+ d->state=Polling;
}
void Aurora::startDownload()
#include <QObject>
#include <QUrl>
+#ifdef AURORA_LIBRARY_BUILD
+#define AURORA_EXPORT Q_DECL_EXPORT
+#else
+#define AURORA_EXPORT Q_DECL_IMPORT
+#endif
+
+namespace AuroraUpdater {
+
class AuroraPrivate;
-class Aurora:public QObject
+/** The Aurora class is the main interface for the automatic updater.
+ *
+ * Normally you just instantiate it. You only need to set the base URL and the
+ * polling interval once - next time the instance will remember the settings.
+ * */
+class AURORA_EXPORT Aurora:public QObject
{
Q_OBJECT
public:
+ ///instantiates the updater object, normally you should only need one of them
explicit Aurora(QObject*parent=0);
+ ///deletes the updater object (stopping any update that is in progress)
virtual ~Aurora();
+ ///returns the currently set base URL
QUrl baseUrl()const;
+ ///returns the name of the index file that contains meta data about releases,
+ ///the name of the signature file is inferred to be the same plus ".asc"
+ QString indexFileName()const;
+ ///State of the Updater object
enum State{
+ ///the instance is initializing
Initializing=0,
+ ///the instance is currently polling for an update on meta data
Polling=1,
+ ///the instance is currently verifying the meta data
Checking=5,
+ ///the instance is downloading a new release
Downloading=2,
+ ///the instance has verified ...
Installing=3,
PostInstall=4
};
int pollInterVall()const;
public slots:
- void setBaseUrl(QUrl);
+ void setBaseUrl(QUrl baseurl,QString indexfile);
void startPoll(int intervalInSeconds=-1);
void setPollIntervall(int seconds);
void startDownload();
private slots:
void pollnow();
+ void abortCycle();
+ void checkMetaFile();
private:
AuroraPrivate *d;
};
+//end of namespace
+}
+
+using namespace AuroraUpdater;
+
#endif
--- /dev/null
+// Copyright (C) 2012-2013 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU LGPL version 3 or at your option any newer.
+// See COPYING.LGPL file that comes with this distribution.
+//
+
+#include "subtask.h"
+#include <QDebug>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+
+bool AuroraUpdater::SubTask::add(QList< QUrl > urls)
+{
+ qDebug()<<"SubTask::add"<<urls;
+ if(urls.size()==0)return true;//this is a fake true: we do nothing instantly
+ if(mstate!=Working){
+ qDebug()<<"SubTask: not working anymore, cannot add more URLs now";
+ return false;
+ }
+ if(mnam==nullptr)
+ mnam=new QNetworkAccessManager(this);
+ //go through urls, check for validity
+ for(auto url:urls)
+ if(!url.isValid())return false;
+ //go through urls, create network requests
+ for(auto url:urls){
+ //are we already downloading it?
+ bool found=false;
+ for(const auto &entry:mreq.keys())
+ if(entry==url){
+ found=true;
+ break;
+ }
+ if(found)continue;
+ //start downloading
+ QNetworkReply*rep=mnam->get(QNetworkRequest(url));
+ mreq.insert(url,rep);
+ connect(rep,SIGNAL(finished()),this,SLOT(finishedReq()));
+ connect(rep,SIGNAL(error(QNetworkReply::NetworkError)), this,SLOT(errorReq()));
+ connect(rep,SIGNAL(readyRead()),this,SLOT(readFromNet()));
+ }
+ return true;
+}
+
+void AuroraUpdater::SubTask::finishedReq()
+{
+// qDebug()<<"SubTask::finishedReq"<<hex<<(quint64)sender();
+ if(mstate!=Working){
+ qDebug()<<"SubTask: not working anymore, ignoring fake successs";
+ return;
+ }
+ //find the sub-task
+ QNetworkReply*rp=qobject_cast<QNetworkReply*>(sender());
+ if(rp==nullptr)return;
+ for(auto &rq:mreq)
+ if(rp==rq.reply){
+ rq.copyNet2Local();
+// qDebug()<<"SubTask::finishedReq: deleting connection";
+ rq.reply->deleteLater();
+ rq.reply=nullptr;
+ }
+ //is everything finished?
+ for(const auto &rq:mreq){
+ if(!rq.reply.isNull())return;
+ }
+ qDebug()<<"SubTask: I'm done, emitting finished()";
+ mstate=Success;
+ emit finished();
+}
+
+void AuroraUpdater::SubTask::errorReq()
+{
+// qDebug()<<"SubTask::errorReq"<<hex<<(quint64)sender();
+ if(mstate!=Working){
+ qDebug()<<"SubTask::errorReq: not working anymore, ignoring follow-up error";
+ return;
+ }
+ mstate=Error;
+ if(mreq.size()==0)return;
+ //abort all requests
+ for(const auto &r:mreq){
+ if(!r.reply.isNull()){
+ if(r.reply != sender()){
+// qDebug()<<"SubTask::errorReq: aborting connection";
+ r.reply->abort();
+ }
+// qDebug()<<"SubTask::errorReq: deleting connection";
+ r.reply->deleteLater();
+ }
+ }
+ mreq.clear();
+ //signal my own error
+ qDebug()<<"SubTask::errorReq: done, emitting error";
+ emit error();
+}
+
+void AuroraUpdater::SubTask::readFromNet()
+{
+// qDebug()<<"SubTask::readFromNet"<<hex<<(quint64)sender();
+ QNetworkReply*r=qobject_cast<QNetworkReply*>(sender());
+ if(r==nullptr)return;
+ for(auto &rq:mreq)
+ if(rq.reply == r)
+ rq.copyNet2Local();
+}
+
+void AuroraUpdater::SubTasklet::copyNet2Local()
+{
+// qDebug()<<"SubTasklet::copyNet2Local, this="<<hex<<(quint64)this;
+ if(reply.isNull())return;
+ if(reply->bytesAvailable()<1)return;
+ file->seek(file->size());
+ file->write(reply->readAll());
+}
--- /dev/null
+// Copyright (C) 2012 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU LGPL version 3 or at your option any newer.
+// See COPYING.LGPL file that comes with this distribution.
+//
+
+#ifndef AURORA_SUBTASK_H
+#define AURORA_SUBTASK_H
+
+#include <QString>
+#include <QObject>
+#include <QUrl>
+#include <QMap>
+#include <QPointer>
+#include <QTemporaryFile>
+#include <QNetworkReply>
+
+class QNetworkAccessManager;
+namespace AuroraUpdater {
+
+/// \internal helper class for the SubTask - it tracks a single connection/url
+struct SubTasklet {
+ ///the open reply object
+ QPointer<QNetworkReply>reply;
+ ///temporary file to hold the data
+ QSharedPointer<QTemporaryFile> file;
+
+ ///used by QMap only, invalid instance
+ SubTasklet(){}
+ ///normal way to instantiate it: from the fresh reply object, auto-creates a temporary file
+ SubTasklet(QNetworkReply*r):reply(r),file(new QTemporaryFile){file->open();}
+ ///copy constructor
+ SubTasklet(const SubTasklet&t):reply(t.reply),file(t.file){}
+ ///copy operator
+ SubTasklet& operator=(const SubTasklet&)=default;
+ ///used by SubTask to trigger copying data from the reply to the temp file
+ void copyNet2Local();
+};
+
+///Instances of this class track the retrieval of one or more URLs and
+///signal finished() when all of them downloaded successfully or error()
+///if any of them failed. In case of an error all running downloads are
+///aborted.
+class SubTask:public QObject {
+ Q_OBJECT
+ public:
+ ///instantiates a SubTask with no running downloads, use add to start downloading
+ SubTask(QObject*parent=nullptr):SubTask(QList<QUrl>(),parent){}
+ ///instantiates a SubTask and starts downloading with one initial URL
+ SubTask(QUrl u,QObject*parent=nullptr):SubTask(QList<QUrl>()<<u,parent){}
+ ///instantiates a SubTask and starts downloading with a list of initial URLs
+ SubTask(QList<QUrl>u,QObject*parent=nullptr):QObject(parent){add(u);}
+
+ ///adds a single URL to its downloads,
+ ///this function refuses to add downloads after the SubTask has already finished
+ ///\param url the URL to download
+ ///\returns true if the URL was successfully added, false if the URL was not valid
+ bool add(QUrl url){return add(QList<QUrl>()<<url);}
+ ///adds a several URLs to its downloads, in case any of the URLs is not valid it does
+ ///not start downloading any of them,
+ ///this function refuses to add downloads after the SubTask has already finished
+ ///\param urls the URLs to download
+ ///\returns true if the URLs were successfully added, false if any of the URLs was not valid
+ bool add(QList<QUrl>urls);
+
+ ///returns all URLs that this SubTask tries to download or has already downloaded
+ QList<QUrl> urls()const{return mreq.keys();}
+ ///returns the temporary file corresponding to the given URL,
+ ///after finished() has been triggered this file contains the completely
+ ///downloaded data
+ QFile& file(QUrl u){return *mreq[u].file;}
+
+ ///represents the internal state of the object
+ enum State{
+ ///the object has not started or is in the process of downloading
+ Working,
+ ///the SubTask has finished with an error
+ Error,
+ ///the SubTask has successfully finished
+ Success
+ };
+
+ ///returns the current state of the instance
+ State state()const{return mstate;}
+
+ private slots:
+ /// \internal reacts to finished downloads
+ void finishedReq();
+ /// \internal reacts to network errors
+ void errorReq();
+ /// \internal reads data from open connections
+ void readFromNet();
+
+ signals:
+ ///emitted if/when a network error occurs, all downloads are aborted
+ void error();
+ ///emitted when all downloads have finished successfully
+ void finished();
+ private:
+ //network handling
+ QNetworkAccessManager*mnam=nullptr;
+ //all added URLs and corresponding requests and temp files
+ QMap<QUrl,SubTasklet>mreq;
+ //current internal state
+ State mstate=Working;
+};
+
+//end of namespace
+}
+
+#endif
\ No newline at end of file
--- /dev/null
+#include "../../aurora/src/aurora.h"
\ No newline at end of file