aurora: download index
authorKonrad Rosenbaum <konrad@silmor.de>
Sat, 20 Jul 2013 11:53:53 +0000 (13:53 +0200)
committerKonrad Rosenbaum <konrad@silmor.de>
Sat, 20 Jul 2013 11:53:53 +0000 (13:53 +0200)
aurora.pri [new file with mode: 0644]
aurora/aurora.pro
aurora/build-gpg.sh
aurora/src/aurora.cpp
aurora/src/aurora.h
aurora/src/subtask.cpp [new file with mode: 0644]
aurora/src/subtask.h [new file with mode: 0644]
include/aurora/Aurora [new file with mode: 0644]

diff --git a/aurora.pri b/aurora.pri
new file mode 100644 (file)
index 0000000..f6b4b1a
--- /dev/null
@@ -0,0 +1,6 @@
+#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
index 566187c..d9de6bb 100644 (file)
@@ -11,9 +11,21 @@ RCC_DIR = .ctmp
 
 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
index 2a13096..5d939ee 100755 (executable)
@@ -1,3 +1,4 @@
+#!/bin/bash
 set -e
 
 fmake(){
@@ -47,3 +48,11 @@ fmake doc
 fmake tests
 make
 make install
+
+
+#clean up and optimization
+cd ..
+rm gpg/bin/*-config
+strip gpg/bin/* || true
+echo
+echo Done.
index 6489e94..4c964a0 100644 (file)
@@ -1,22 +1,28 @@
-// 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;}
                 
@@ -39,19 +45,23 @@ Aurora::~Aurora()
 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();
 }
 
@@ -73,6 +83,7 @@ int Aurora::pollInterVall() const
 void Aurora::setPollIntervall(int seconds)
 {
         if(seconds<0)seconds=0;
+        qDebug()<<"Aurora: Changing Interval to"<<seconds<<"s";
         startPoll(seconds);
 }
 
@@ -89,14 +100,22 @@ void Aurora::startPoll(int intervalInSeconds)
                 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
@@ -113,9 +132,41 @@ bool Aurora::isReadyForInstall() 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()
index ef2c4d9..2ae0373 100644 (file)
 #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
                 };
@@ -44,7 +69,7 @@ class Aurora:public QObject
                 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();
@@ -60,8 +85,15 @@ class Aurora:public QObject
                 
         private slots:
                 void pollnow();
+                void abortCycle();
+                void checkMetaFile();
         private:
                 AuroraPrivate *d;
 };
 
+//end of namespace
+}
+
+using namespace AuroraUpdater;
+
 #endif
diff --git a/aurora/src/subtask.cpp b/aurora/src/subtask.cpp
new file mode 100644 (file)
index 0000000..77e596a
--- /dev/null
@@ -0,0 +1,114 @@
+// 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());
+}
diff --git a/aurora/src/subtask.h b/aurora/src/subtask.h
new file mode 100644 (file)
index 0000000..b6c5b23
--- /dev/null
@@ -0,0 +1,110 @@
+// 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
diff --git a/include/aurora/Aurora b/include/aurora/Aurora
new file mode 100644 (file)
index 0000000..4bffe7f
--- /dev/null
@@ -0,0 +1 @@
+#include "../../aurora/src/aurora.h"
\ No newline at end of file