nzb-0.2/0000755000175000017500000000000011731653621012453 5ustar mnordstrmnordstrnzb-0.2/src/0000755000175000017500000000000011731653621013242 5ustar mnordstrmnordstrnzb-0.2/src/mainwindow.h0000644000175000017500000000646111731371754015602 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: mainwindow.h,v 1.6 2005/10/03 19:04:55 mnordstr Exp $ * This file provides the GUI. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include "nzbdata.h" #include "downloader.h" #include "decoder.h" #include "output.h" #include "ui_options.h" class QAction; class QMenu; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); void loadFile(const QString &fileName); bool shuttingDown() const { return shutting_down; } QSettings* getConfig() { return config; } void setConfig(QSettings *config) { this->config = config; } QTableWidgetItem* getDownloaderItem(int thread_id) { return d->item(thread_id, 0); } public slots: void downloadEvent(QString message, int row, int type); void decodeEvent(QString message, int row, int type); void outputEvent(QString message, int type); protected: void closeEvent(QCloseEvent *event); private slots: void open(); void about(); void start(); void stop(); void pause(); void resume(); void stream(); void options(); void sortList(); void clearList() { clearData(); } void restartDownloaders(); signals: void processDecoder(); void processOutput(); void processDownloader(); void fullStop(); void requestPause(); void requestResume(); private: void createActions(); void createMenus(); void createToolBars(); void createStatusBar(); void readSettings(); void writeSettings(); void clearData(); void populateList(); QTableWidget *l, *d; QSplitter *split; QTableView *view; NzbList nzblist; QList downloaders; QList decoders; Output *output; QMutex file_lock; //QWaitCondition dc_done; QMenu *fileMenu; QMenu *editMenu; QMenu *helpMenu; QMenu *toolsMenu; QMenu *actionsMenu; QToolBar *fileToolBar, *actionToolBar; QAction *openAct; QAction *exitAct; QAction *aboutAct; QAction *optionsAct; QAction *startAct; QAction *stopAct; QAction *pauseAct; QAction *resumeAct; QAction *streamAct; QAction *sortAct; QAction *clearAct; QLabel *totalSpeed; QLabel *totalSize; QProgressBar *totalProgress; bool shutting_down; bool download_complete; QSettings *config; }; class OptionsDlg : public QDialog { Q_OBJECT public: OptionsDlg(QWidget *parent = 0); private: Ui::OptionsDlg ui; QObject *parent; private slots: void accept(); void reject(); void browseSave(); void browseMedia(); void enableGuess(int state); void sslChange(int state); void useFile(); }; #endif nzb-0.2/src/output.cpp0000644000175000017500000002710311212526255015305 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: output.cpp,v 1.12 2005/10/16 12:03:32 mnordstr Exp $ * This file provides the output functions. */ #include "output.h" #include "mainwindow.h" Output::Output(NzbList *nzblist, int thread_id, QMutex *file_lock, QObject *parent) { this->nzblist = nzblist; this->parent = parent; this->thread_id = thread_id; this->file_lock = file_lock; connect(this, SIGNAL(outputEvent(QString, int)), parent, SLOT(outputEvent(QString, int))); } void Output::run() { qDebug("Output running."); file = 0; seg = 0; ms = NULL; streamer = NULL; mp = NULL; player = NULL; fout = NULL; first_output = true; last_file = -1; fn = ""; this->exec(); qDebug("Output done."); } void Output::process() { if (stream && ((MainWindow*)parent)->getConfig()->value("output/stream").toBool() && file == 0 && seg == 0 && streamer == NULL) { streamer = new QTcpServer(); connect(streamer, SIGNAL(newConnection()), this, SLOT(newConnection()), Qt::DirectConnection); streamer->listen(QHostAddress::Any, 4321); qDebug("Starting media player"); if (!((MainWindow*)parent)->getConfig()->value("output/mediaplayer").toString().isEmpty()) { player = new DetachedPlayer(((MainWindow*)parent)->getConfig()->value("output/mediaplayer").toString()+" http://127.0.0.1:4321/"); player->start(); qDebug("Media player started"); } else { qDebug("No media player specified, none launched"); } qDebug("Waiting for connection..."); } if (stream && ((MainWindow*)parent)->getConfig()->value("output/stream").toBool() && ms == NULL) { QTimer::singleShot(1000, this, SLOT(process())); return; } if (getNext(&file, &seg)) { if (nzblist->getFile(file)->getSegment(seg)->getEncStatus() != NZB_ENC_ERROR) { writeOut(file, seg); } nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_DONE); } } bool Output::getNext(int *file, int *seg) { if (nzblist->getList()->size() == 0) return false; file_lock->lock(); for (;;) { if (nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_NONE || nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DOWNLOADING || nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DOWNLOADED || nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DL_SKIPPED) { file_lock->unlock(); return false; } if (nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DECODING) { file_lock->unlock(); return false; } if (nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DECODED) { nzblist->getFile(*file)->getSegment(*seg)->setStatus(NZB_PROCESSING); file_lock->unlock(); return true; } if (nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DEC_SKIPPED) { if (*file == nzblist->getList()->size()-1 && *seg == nzblist->getFile(*file)->getSegments()->size()-1) { emit outputEvent("", 1); } } if (nzblist->getFile(*file)->getSegments()->size() == *seg+1) { if (nzblist->getList()->size() == *file+1) { file_lock->unlock(); return false; } *file = *file + 1; *seg = 0; } else { *seg = *seg + 1; } } file_lock->unlock(); return false; } void Output::writeOut(int file, int seg) { int hdrsize = 0; const char rarhdr[] = {0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00, '\0'}; // RAR Format header + null character. bool streamsrv = false; if (((MainWindow*)parent)->getConfig()->value("output/stream").toBool()) { streamsrv = true; } if (!stream) { if (last_file != file) { last_file = file; fn = ((MainWindow*)parent)->getConfig()->value("output/savepath").toString(); if (fn.isEmpty()) { emit outputEvent("Please specify a save path under the output tab in options.", 2); return; } fn += "/"; if (((MainWindow*)parent)->getConfig()->value("output/subfolders").toBool()) { QString fileName; int pos = nzblist->getFile(file)->getNzbFileName().lastIndexOf(QRegExp("[/\\\\]")); if (pos != -1) { fileName = nzblist->getFile(file)->getNzbFileName().right(nzblist->getFile(file)->getNzbFileName().length() - pos - 1); } else { fileName = nzblist->getFile(file)->getNzbFileName(); } if (((MainWindow*)parent)->getConfig()->value("output/guessalbum").toBool()) { pos = fileName.indexOf(QRegExp("[-]")); if (pos != -1) { QString first = fileName.mid(0, pos); QString last = fileName.mid(pos+1); if (first.left(6) == "msgid_") { first = first.mid(first.indexOf(QRegExp("[_]"), 6)); } if (last.right(4) == ".nzb") { last = last.left(last.length() - 4); } first.replace("_", " "); last.replace("_", " "); first = first.trimmed(); last = last.trimmed(); QDir dir; if (!dir.exists(fn+first+"/"+last)) { if (dir.mkpath(fn+first+"/"+last)) { fn += first+"/"+last+"/"; } } else { fn += first+"/"+last+"/"; } } else if (fileName.right(4) == ".nzb") { QDir dir; if (!dir.exists(fn+fileName.left(fileName.length() - 4))) { if (dir.mkdir(fn+fileName.left(fileName.length() - 4))) { fn += fileName.left(fileName.length() - 4)+"/"; } } else { fn += fileName.left(fileName.length() - 4)+"/"; } } else { QDir dir; if (!dir.exists(fn+fileName)) { if (dir.mkdir(fn+fileName)) { fn += fileName+"/"; } } else { fn += fileName+"/"; } } } else if (fileName.right(4) == ".nzb") { QDir dir; if (!dir.exists(fn+fileName.left(fileName.length() - 4))) { if (dir.mkdir(fn+fileName.left(fileName.length() - 4))) { fn += fileName.left(fileName.length() - 4)+"/"; } } else { fn += fileName.left(fileName.length() - 4)+"/"; } } else { QDir dir; if (!dir.exists(fn+fileName)) { if (dir.mkdir(fn+fileName)) { fn += fileName+"/"; } } else { fn += fileName+"/"; } } } /*for (int i = 0; i < nzblist->getFile(file)->getSegments()->size(); i++) { if (nzblist->getFile(file)->getSegments()->at(i).getFilename() != "") { fn += nzblist->getFile(file)->getSegments()->at(i).getFilename(); qDebug() << "Opening " << fn; break; } }*/ //fn += nzblist->getFile(file)->getSegment(seg)->getFilename(); fout = NULL; /*fout = new QFile(fn); fout->open(QIODevice::WriteOnly);*/ } if (fn.right(1) == "/") { fn += nzblist->getFile(file)->getSegment(seg)->getFilename(); /*if (fn.right(1) == "/") { Filename guessing based on subject. QRegExp rx("* (1addslashhere*)"); rx.setPatternSyntax(QRegExp::Wildcard); if (rx.exactMatch(nzblist->getFile(file)->getSubject().right(11))) { qDebug() << "Guessing filename..."; int pos = nzblist->getFile(file)->getSubject().lastIndexOf(" "); int pos2 = nzblist->getFile(file)->getSubject().lastIndexOf(" ", pos-2); fn += nzblist->getFile(file)->getSubject().mid(pos2+1, pos-(pos2+1)); } }*/ fout = new QFile(fn); fout->open(QIODevice::WriteOnly); } if (fout != NULL && fout->error() == QFile::NoError) { fout->write(*(nzblist->getFile(file)->getSegment(seg)->getDecoded())); if (seg == nzblist->getFile(file)->getSegments()->size()-1) { fout->close(); delete fout; fout = NULL; } } if (file == nzblist->getList()->size()-1 && seg == nzblist->getFile(file)->getSegments()->size()-1) { emit outputEvent("", 1); } } else { if (first_output) { first_output = false; if (streamsrv) { qDebug("Sending reply"); QString reply = "HTTP/1.0 200 OK\nServer: nzb/"+QString(NZB_VERSION)+"\nConnection: close\nContent-Type: application/octet-stream\n\n"; ms->write(reply.toAscii()); qDebug("Reply sent"); } else { if (((MainWindow*)parent)->getConfig()->value("output/mediaplayer").toString().isEmpty()) { emit outputEvent("Please specify a media player under the output tab in options.", 2); return; } mp = new QProcess(); mp->start(((MainWindow*)parent)->getConfig()->value("output/mediaplayer").toString().toAscii()); mp->waitForStarted(); } } if (!streamsrv && mp == NULL) { return; } if (seg == 0 && strcmp((nzblist->getFile(file)->getSegment(seg)->getDecoded()->mid(0, 7)), rarhdr) == 0) { qDebug() << "nzb: RAR format detected, extracting.\n"; if (nzblist->getFile(file)->getSegment(seg)->getDecoded()->at(45) == 0x30) { hdrsize = (unsigned int)nzblist->getFile(file)->getSegment(seg)->getDecoded()->at(25); if (streamsrv) { ms->write(nzblist->getFile(file)->getSegment(seg)->getDecoded()->mid(20 + hdrsize, nzblist->getFile(file)->getSegment(seg)->getDecoded()->length() - (20 + hdrsize))); } else { mp->write(nzblist->getFile(file)->getSegment(seg)->getDecoded()->mid(20 + hdrsize, nzblist->getFile(file)->getSegment(seg)->getDecoded()->length() - (20 + hdrsize))); //mp->waitForBytesWritten(-1); } } else { qDebug() << "nzb: [Error] Compressed RAR, can't decompress.\n"; emit outputEvent("Compressed RAR, can't decompress.", 2); } } else { QStringList notok; QString src; notok << ".nzb" << ".sfv" << ".par" << ".par2" << ".nfo" << ".jpg"; src = nzblist->getFile(file)->getSegment(seg)->getFilename().mid(nzblist->getFile(file)->getSegment(seg)->getFilename().length() - 4, 4); if (!notok.contains(src.toLower())) { if (streamsrv) { ms->write(*(nzblist->getFile(file)->getSegment(seg)->getDecoded())); //ms->waitForBytesWritten(-1); } else { mp->write(*(nzblist->getFile(file)->getSegment(seg)->getDecoded())); //mp->waitForBytesWritten(-1); } } } if (file == nzblist->getList()->size()-1 && seg == nzblist->getFile(file)->getSegments()->size()-1) { emit outputEvent("", 1); } } nzblist->getFile(file)->getSegment(seg)->setDecoded(""); } void Output::stop() { bool streamsrv = false; if (((MainWindow*)parent)->getConfig()->value("output/stream").toBool()) { streamsrv = true; } if (stream) { if (streamsrv) { if (ms) ms->close(); if (streamer) streamer->close(); } else { if (mp) { mp->closeWriteChannel(); qDebug("Waiting for write finish..."); mp->waitForFinished(-1); qDebug("Done waiting"); delete mp; } this->exit(); } } } void Output::closeStream() { qDebug("Streamer disconneced"); if (player) { player->quit(); player->wait(); delete player; } //if (ms) delete ms; //if (streamer) delete streamer; qDebug("Killing output"); this->exit(); } void Output::newConnection() { qDebug("Got connection"); ms = streamer->nextPendingConnection(); connect(ms, SIGNAL(disconnected()), this, SLOT(closeStream())); qDebug("Waiting for request"); ms->waitForReadyRead(5000); } void DetachedPlayer::run() { QProcess::execute(cmd); } nzb-0.2/src/decoder.cpp0000644000175000017500000004027511167750120015356 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: decoder.cpp,v 1.8 2005/10/16 12:03:31 mnordstr Exp $ * This file provides the decoders. */ #include "decoder.h" #include "mainwindow.h" Decoder::Decoder(NzbList *nzblist, int thread_id, QMutex *file_lock, QObject *parent) { this->nzblist = nzblist; this->parent = parent; this->thread_id = thread_id; this->file_lock = file_lock; connect(this, SIGNAL(decodeEvent(QString, int, int)), parent, SLOT(decodeEvent(QString, int, int))); } void Decoder::run() { qDebug("Decoder running."); emit decodeEvent("Idle", thread_id); file = 0; seg = 0; processing = false; this->exec(); emit decodeEvent("Halted", thread_id); qDebug("Decoder done."); } void Decoder::stop() { this->exit(); } void Decoder::processFiles() { if (getNext(&file, &seg)) { emit decodeEvent("Decoding ["+QString::number(file+1)+": "+QString::number(seg+1)+"/"+QString::number(nzblist->getFile(file)->getSegments()->size())+"] "+nzblist->getFile(file)->getSegment(seg)->getMsgid(), thread_id); processing = true; //QTimer::singleShot(0, this, SLOT(processFile())); processFile(); } } void Decoder::processFile() { if (nzblist->getFile(file)->getEncoding() == NZB_ENC_YENC || nzblist->getFile(file)->getEncoding() == NZB_UNDEF) { yDecode(file, seg); } if (nzblist->getFile(file)->getEncoding() == NZB_ENC_UENC || nzblist->getFile(file)->getEncoding() == NZB_UNDEF) { if (seg == 0) qDebug("No yEnc data found, trying uudecoding."); if (nzblist->getFile(file)->getSegments()->size() == 1) { uDecode(file, seg); } else { if (seg == 0) uDecode(file, seg, true, 1); else if (seg == nzblist->getFile(file)->getSegments()->size() - 1) uDecode(file, seg, true, 3); else uDecode(file, seg, true, 2); } if (nzblist->getFile(file)->getSegment(seg)->getEncStatus() == NZB_URES_ERROR) { nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_ENC_ERROR); } } nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_DECODED); processing = false; //emit decodeEvent("Idle", thread_id, 1); emit decodeEvent("Decoded ["+QString::number(file+1)+": "+QString::number(seg+1)+"/"+QString::number(nzblist->getFile(file)->getSegments()->size())+"] "+nzblist->getFile(file)->getSegment(seg)->getMsgid(), thread_id, 1); } bool Decoder::getNext(int *file, int *seg) { if (nzblist->getList()->size() == 0) return false; if (processing == true) return false; file_lock->lock(); for (;;) { if (nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DOWNLOADING || nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_NONE) { file_lock->unlock(); emit decodeEvent("Idle", thread_id, 2); return false; } if (nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DOWNLOADED) { nzblist->getFile(*file)->getSegment(*seg)->setStatus(NZB_DECODING); file_lock->unlock(); return true; } if (nzblist->getFile(*file)->getSegment(*seg)->getStatus() == NZB_DL_SKIPPED) { nzblist->getFile(*file)->getSegment(*seg)->setStatus(NZB_DEC_SKIPPED); file_lock->unlock(); emit decodeEvent("Idle", thread_id, 1); return false; } if (nzblist->getFile(*file)->getSegments()->size() == *seg+1) { if (nzblist->getList()->size() == *file+1) { file_lock->unlock(); emit decodeEvent("Idle", thread_id, 3); return false; } *file = *file + 1; *seg = 0; } else { *seg = *seg + 1; } } file_lock->unlock(); return false; } void Decoder::yDecode(int file, int seg) { // This decoder has been written to comply with yEnc revision 1.3 // http://www.yenc.org/yenc-draft.1.3.txt int i; int crc32; QString pcrc32, header; QByteArray decdata = ""; int ybegin, begin, end, pos = 0, pos2 = 0; QString *data = nzblist->getFile(file)->getSegment(seg)->getData(); // Initialize struct with defaults. nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_YRES_ERROR); nzblist->getFile(file)->getSegment(seg)->setFilename(""); nzblist->getFile(file)->getSegment(seg)->setPart(0); nzblist->getFile(file)->getSegment(seg)->setSize(0); nzblist->getFile(file)->getSegment(seg)->setTotal(0); nzblist->getFile(file)->getSegment(seg)->setTotalSize(0); nzblist->getFile(file)->getSegment(seg)->setCrc32(""); nzblist->getFile(file)->getSegment(seg)->setBlockBegin(0); nzblist->getFile(file)->getSegment(seg)->setBlockEnd(0); // Check for yenc encoded data. if ((pos = data->indexOf("=ybegin ", 0)) == -1) { return; } if (seg == 0) { nzblist->getFile(file)->setEncoding(NZB_ENC_YENC); } ybegin = pos; pos2 = data->indexOf("\r\n", pos); begin = pos2 + 2; header = data->mid(ybegin, 2048); // part keyword, also means that this is a multi-part file. if ((pos = header.indexOf(" part=")) != -1 && pos < (begin-ybegin)) { pos2 = data->indexOf("\r\n", begin); begin = pos2 + 2; pos2 = data->indexOf(QRegExp("[ \r\n]"), ybegin+pos+1); nzblist->getFile(file)->getSegment(seg)->setPart((data->mid(ybegin + pos + 6, pos2 - (ybegin + pos + 6))).toInt()); } // total keyword. if ((pos = header.indexOf(" total=")) != -1 && pos < (begin-ybegin)) { pos2 = header.indexOf(QRegExp("[ \r\n]"), pos+1); nzblist->getFile(file)->getSegment(seg)->setTotal((header.mid(pos + 7, pos2 - (pos + 7))).toInt()); } // size keyword. if ((pos = data->indexOf(" size=", ybegin)) != -1 && pos < begin) { pos2 = data->indexOf(QRegExp("[ \r\n]"), pos+1); nzblist->getFile(file)->getSegment(seg)->setTotalSize((data->mid(pos + 6, pos2 - (pos + 6))).toLong()); } // name keyword. if ((pos = data->indexOf(" name=", ybegin)) != -1 && pos < begin) { pos2 = data->indexOf("\r\n", pos); nzblist->getFile(file)->getSegment(seg)->setFilename(data->mid(pos + 6, pos2 - (pos + 6)).remove("\"")); } // Multi-part messages have their own =ypart header line. if (nzblist->getFile(file)->getSegment(seg)->getPart() != 0) { if ((pos = data->indexOf("\r\n", ybegin)) != -1 && pos < begin) { if (data->mid(pos + 2, 7) != "=ypart ") { return; } ybegin = pos + 2; // begin keyword. if ((pos = data->indexOf(" begin=", ybegin)) != -1 && pos < begin) { pos2 = data->indexOf(QRegExp("[ \r\n]"), pos+1); nzblist->getFile(file)->getSegment(seg)->setBlockBegin((data->mid(pos + 7, pos2 - (pos + 7))).toLong()); } // end keyword. if ((pos = data->indexOf(" end=", ybegin)) != -1 && pos < begin) { pos2 = data->indexOf(QRegExp("[ \r\n]"), pos+1); nzblist->getFile(file)->getSegment(seg)->setBlockEnd((data->mid(pos + 5, pos2 - (pos + 5))).toLong()); } } else { return; } } if ((pos = data->lastIndexOf("=yend ")) == -1) { return; } end = pos; // size keyword. if ((pos = data->indexOf(" size=", end)) != -1) { pos2 = data->indexOf(QRegExp("[ \r\n]"), pos+1); nzblist->getFile(file)->getSegment(seg)->setSize((data->mid(pos + 6, pos2 - (pos + 6))).toLong()); decdata.reserve(nzblist->getFile(file)->getSegment(seg)->getSize()); } // part keyword. if ((pos = data->indexOf(" part=", end)) != -1) { pos2 = data->indexOf(QRegExp("[ \r\n]"), pos+1); if (nzblist->getFile(file)->getSegment(seg)->getPart() != (data->mid(pos + 6, pos2 - (pos + 6))).toInt()) { nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_YRES_ERRPART); return; } } // pcrc32 keyword. if ((pos = data->indexOf(" pcrc32=", end)) != -1) { pos2 = data->indexOf(QRegExp("[ \r\n]"), pos+1); pcrc32 = data->mid(pos + 8, pos2 - (pos + 8)); } // crc32 keyword. if ((pos = data->indexOf(" crc32=", end)) != -1) { pos2 = data->indexOf(QRegExp("[ \r\n]"), pos+1); nzblist->getFile(file)->getSegment(seg)->setCrc32(data->mid(pos + 7, pos2 - (pos + 7))); } *data = data->mid(begin, end - begin); crcInit(&crc32); unsigned char c; for (i=0; ilength(); i++){ c = (unsigned char)data->at(i).toAscii(); if (c == '\r' && data->at(i+1) == '\n') { i = i + 2; if (i == data->length()) continue; c = (unsigned char)data->at(i).toAscii(); } if (c == '=') { i++; c = (unsigned char)data->at(i).toAscii(); if (c == 0) continue; if (c-64 < 0) { c = c+256; } c = c-64; } if (c-42 < 0) { c = c+256; } decdata += c-42; crcAdd(&crc32, c-42); } if (pcrc32 != "" && !crc32Check(crc32, pcrc32)) { nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_YRES_ERRPCRC32); } else { nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_YRES_OK); } if (decdata.length() != nzblist->getFile(file)->getSegment(seg)->getSize()) { nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_YRES_ERRSIZE); } if (nzblist->getFile(file)->getSegment(seg)->getPart() != 0 && (nzblist->getFile(file)->getSegment(seg)->getBlockEnd() - (nzblist->getFile(file)->getSegment(seg)->getBlockBegin()-1)) != nzblist->getFile(file)->getSegment(seg)->getSize()) { nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_YRES_ERRSIZE); } nzblist->getFile(file)->getSegment(seg)->setDecoded(decdata); nzblist->getFile(file)->getSegment(seg)->setData(""); } bool Decoder::crc32Check(int crc32, QString c_crc) { unsigned int crc; QString calc_crc; crc = crc32 ^ 0xFFFFFFFFl; calc_crc = QString("%1").arg(crc, 8, 16, QChar('0')); c_crc = QString("%1").arg(c_crc, 8, QChar('0')); if (c_crc == calc_crc) { return true; } else { qDebug() << "yEnc CRC " << calc_crc << " != " << c_crc << "\n\n"; return false; } } /* Single character decode. */ #define DEC(Char) (((Char) - ' ') & 077) void Decoder::uDecode(int file, int seg, bool multi, int type) { QString decdata = ""; int pos = 0, pos2 = 0, begin = 0, end; bool badnl = false; // Newlines only \n, not \r\n, int nlcount = 2; QString *data = nzblist->getFile(file)->getSegment(seg)->getData(); nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_URES_ERROR); nzblist->getFile(file)->getSegment(seg)->setFilename(""); // Check for uencoded data. if (!(multi && type != 1) && (pos = data->indexOf("begin ", 0)) == -1) { return; } // Check if yenc data has been missed. if (!(multi && type != 1) && (data->indexOf("=ybegin ", 0)) != -1) { return; } if (!multi || (multi && type == 1)) if (data->at(pos+6).isDigit() && data->at(pos+7).isDigit() && data->at(pos+8).isDigit() && data->at(pos+9) == ' ') { if ((pos2 = data->indexOf("\r\n", pos+10)) == -1) { badnl = true; nlcount = 1; if ((pos2 = data->indexOf("\n", pos+10)) == -1) { return; } } begin = pos2 + nlcount; nzblist->getFile(file)->getSegment(seg)->setFilename(data->mid(pos + 10, pos2 - (pos + 10))); } else { return; } if (!multi || (multi && type == 3)) { if ((pos = data->indexOf("\r\nend\r\n", begin)) == -1 && (pos = data->indexOf("\nend\n", begin)) == -1) { return; } end = pos; } else { end = data->length() - nlcount; } *data = data->mid(begin, (end + nlcount) - begin); if (seg == 0) { nzblist->getFile(file)->setEncoding(NZB_ENC_UENC); } // uudecode char buf[64]; pos = 0; pos2 = 0; int ok = 1; while ((pos = data->indexOf("\n", pos2)) != -1) { if ((data->mid(pos2, pos + 1 - pos2)).length() > 64) { // Too large, something's wrong. pos2 = pos + 1; continue; } strcpy(buf, (data->mid(pos2, pos + 1 - pos2)).toAscii()); pos2 = pos + 1; if (buf[0] > ' ' && buf[0] <= '`') { int cv_len, i; register unsigned char * p=(unsigned char*)buf; cv_len = DEC(*p++); /* Actually decode the uue data; ensure characters are in range. */ if (ok) for (i=0; i'`') || (p[1]<=' ' || p[1]>'`') || (p[2]<=' ' || p[2]>'`') || (p[3]<=' ' || p[3]>'`') ) { ok = 0; break; } decdata += DEC(*p) << 2 | DEC(p[1]) >> 4; decdata += DEC(p[1]) << 4 | DEC(p[2]) >> 2; decdata += DEC(p[2]) << 6 | DEC(p[3]); } if (*p != '\r' && *p != '\n' && *p != '\0') ok=0; else *p=0; } } nzblist->getFile(file)->getSegment(seg)->setData(""); if (!multi || type == 3) { nzblist->getFile(file)->getSegment(seg)->setDecoded(decdata.left(decdata.length() - 2).toAscii()); // This is something fishy that should be fixed. } else { nzblist->getFile(file)->getSegment(seg)->setDecoded(decdata.toAscii()); } nzblist->getFile(file)->getSegment(seg)->setEncStatus(NZB_URES_OK); } void Decoder::crcInit(int *crc32) { *crc32 = -1L; } void Decoder::crcAdd(int *crc32, int c) { const int crc_tab[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; unsigned long ch1,ch2,cc; cc= (c) & 0x000000ffL; ch1=(*crc32 ^ cc) & 0xffL; ch1=crc_tab[ch1]; ch2=(*crc32>>8L) & 0xffffffL; *crc32=ch1 ^ ch2; } nzb-0.2/src/options.ui0000644000175000017500000002251510521121162015263 0ustar mnordstrmnordstr OptionsDlg 0 0 472 352 nzb Options 20 310 431 33 0 6 Use nzb.ini for settings false Qt::Horizontal 131 31 OK Cancel 10 10 451 291 0 Server 10 0 421 231 8 6 Host: Changing the number of connections requires a restart of the application. true Password: Connections: Port: Username: QLineEdit::Password 0 6 Authenticate Use SSL List 0 0 451 211 10 10 217 80 0 6 Scroll nzb list Sort nzb list Automatically uncheck PAR files Output 0 0 441 221 8 6 Browse HTTP Stream (enable on Windows) false Save Path: Media Player: Browse Save in sub-folders Guess Album/Artist/Title from name host port auth ssl username password connections tabWidget okButton cancelButton use_file autosort par_uncheck saveBrowse mediaplayer stream savepath mediaBrowse subfolders guessalbum hidecompleted nzb-0.2/src/decoder.h0000644000175000017500000000334310512232702015010 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: decoder.h,v 1.1 2005/08/21 16:34:20 mnordstr Exp $ * This file provides the decoders. */ #ifndef DECODER_H #define DECODER_H #include #include #include "nzbdata.h" class Decoder : public QThread { Q_OBJECT public: Decoder(NzbList *nzblist, int thread_id, QMutex *file_lock, QObject *parent = 0); void run(); signals: void decodeEvent(QString message, int row, int type = 0); public slots: void processFiles(); void stop(); private slots: void processFile(); private: bool getNext(int *file, int *seg); void yDecode(int file, int seg); bool crc32Check(int crc32, QString c_crc/*, QString data*/); void uDecode(int file, int seg, bool multi = false, int type = 0); void crcInit(int *crc32); void crcAdd(int *crc32, int c); QObject *parent; NzbList *nzblist; int thread_id; QMutex *file_lock; int file, seg; bool processing; }; #endif nzb-0.2/src/main.cpp0000644000175000017500000000241010512232702014654 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: main.cpp,v 1.1 2005/08/21 16:34:20 mnordstr Exp $ * This file provides the main function. */ #include #include "mainwindow.h" int main(int argc, char *argv[]) { Q_INIT_RESOURCE(nzb); QApplication app(argc, argv); app.setWindowIcon(QIcon(":/images/nzb.png")); MainWindow mainWin; mainWin.show(); if (app.argc() == 2) { mainWin.loadFile(app.argv()[1]); } return app.exec(); } nzb-0.2/src/nzbdata.h0000644000175000017500000001263411425262734015045 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: nzbdata.h,v 1.11 2006/05/14 10:37:53 mnordstr Exp $ * This file provides the nzb data classes. */ #ifndef NZBDATA_H #define NZBDATA_H #include #include #define NZB_VERSION "0.2" #define NZB_UNDEF 0 #define NZB_NONE 15 #define NZB_DOWNLOADING 1 #define NZB_DOWNLOADED 2 #define NZB_DL_SKIPPED 19 #define NZB_DEC_SKIPPED 20 #define NZB_DECODING 3 #define NZB_DECODED 4 #define NZB_PROCESSING 5 #define NZB_DONE 6 #define NZB_ENC_YENC 16 #define NZB_ENC_UENC 17 #define NZB_ENC_ERROR 18 #define NZB_YRES_OK 7 // Ok #define NZB_YRES_ERRSIZE 8 // Part size mismatch #define NZB_YRES_ERRTOTSIZE 9 // Total filesize mismatch #define NZB_YRES_ERRPCRC32 10 // Part CRC32 error #define NZB_YRES_ERROR 11 // General/Unknown error #define NZB_YRES_ERRPART 12 // Part number mismatch #define NZB_URES_OK 13 // Ok #define NZB_URES_ERROR 14 // General/Unknown error class NzbSeg { public: NzbSeg(int bytes, QString msgid); NzbSeg() { } int getBytes() const { return bytes; } QString getMsgid() const { return msgid; } QString* getData() { return &data; } QByteArray* getDecoded() { return &decoded; } int getStatus() const { return status; } void setData(QString data) { this->data = data; } void setDecoded(QByteArray decoded) { this->decoded = decoded; } void setStatus(int status) { this->status = status; } int getEncoding() const { return encoding; } QString getFilename() const { return filename; } int getPart() const { return part; } long getSize() const { return size; } int getTotal() const { return total; } long getTotalSize() const { return total_size; } QString getCrc32() const { return crc32; } long getBlockBegin() const { return block_begin; } long getBlockEnd() const { return block_end; } void setEncoding(int encoding) { this->encoding = encoding; } void setFilename(QString filename) { this->filename = filename; } void setPart(int part) { this->part = part; } void setSize(long size) { this->size = size; } void setTotal(int total) { this->total = total; } void setTotalSize(long total_size) { this->total_size = total_size; } void setCrc32(QString crc32) { this->crc32 = crc32; } void setBlockBegin(long block_begin) { this->block_begin = block_begin; } void setBlockEnd(long block_end) { this->block_end = block_end; } int getEncStatus() const { return enc_status; } void setEncStatus(int enc_status) { this->enc_status = enc_status; } private: int bytes; QString msgid; QString data; QByteArray decoded; int status; int enc_status; int encoding; QString filename; int part; long size; int total; long total_size; QString crc32; long block_begin; long block_end; }; class NzbFile { public: NzbFile(); NzbFile(QString poster, QDateTime date, QString subject); void addGroup(QString group) { groups.append(group); } void addSegment(NzbSeg newseg); QString getPoster() const { return poster; } QDateTime getDate() const { return date; } QString getSubject() const { return subject; } QStringList getGroups() const { return groups; } QList* getSegments() { return &segments; } NzbSeg* getSegment(int seg) { return &(segments[seg]); } QString getNzbFileName() const { return nzbFileName; } void setNzbFileName(QString nzbFile) { this->nzbFileName = nzbFile; } static bool subjectLessThan(const NzbFile &nf1, const NzbFile &nf2); int getBytes() const { return bytes; } int getEncoding() const { return encoding; } void setEncoding(int encoding) { this->encoding = encoding; } private: QString poster; QDateTime date; QString subject; QStringList groups; QList segments; QString nzbFileName; int bytes; int encoding; }; class NzbList { public: void importNzb(QFile *nzbfile); void addFile(NzbFile file) { files.append(file); } QList* getList() { return &files; } NzbFile* getFile(int file) { return &(files[file]); } void clearList() { files.clear(); } void sortList(); int totalParts(); qint64 totalSize(); private: QList files; }; class NzbHandler : public QXmlDefaultHandler { public: NzbHandler(NzbList *parent, QString nzbfile); bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &attributes); bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName); bool characters(const QString &str); bool fatalError(const QXmlParseException &exception); QString errorString() const; private: QString nzbFileName; QString currentText; NzbFile currentFile; QMap fileSegs; int currentBytes, currentSegPos; QString errorStr; bool metNzbTag; NzbList *parent; }; #endif nzb-0.2/src/src.pro0000644000175000017500000000105211425262734014552 0ustar mnordstrmnordstr###################################################################### # Automatically generated by qmake (2.00a) Fri Jul 29 20:53:51 2005 ###################################################################### TEMPLATE = app INSTALLS += target target.path = /usr/bin TARGET = nzb # Input HEADERS += mainwindow.h nzbdata.h downloader.h decoder.h output.h SOURCES += main.cpp \ mainwindow.cpp \ nzbdata.cpp \ downloader.cpp \ decoder.cpp \ output.cpp RESOURCES = nzb.qrc FORMS = options.ui RC_FILE = nzb.rc CONFIG += qt QT += xml network nzb-0.2/src/downloader.cpp0000644000175000017500000002340411425262734016110 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: downloader.cpp,v 1.7 2006/05/14 10:37:53 mnordstr Exp $ * This file provides the NNTP downloader. */ #include "downloader.h" #include "mainwindow.h" Downloader::Downloader(NzbList *nzblist, int thread_id, QMutex *file_lock, QTableWidget *l, QObject *parent) { this->nzblist = nzblist; this->parent = parent; this->thread_id = thread_id; this->file_lock = file_lock; this->l = l; this->use_ssl = false; this->last_failed = false; paused = false; connect(this, SIGNAL(downloadEvent(QString, int, int)), parent, SLOT(downloadEvent(QString, int, int))); } void Downloader::run() { qDebug("Downloader running."); QTimer::singleShot(0, this, SLOT(init())); this->exec(); qDebug("Downloader done."); } void Downloader::init() { file = 0, seg = 0; downloading = false; paused = false; connect(this, SIGNAL(processNext()), this, SLOT(processFiles())); use_ssl = ((MainWindow*)parent)->getConfig()->value("server/ssl").toBool(); emit downloadEvent("Connecting", thread_id); ((MainWindow*)parent)->getDownloaderItem(thread_id)->setIcon(QIcon(":/images/connect_creating.png")); usenet = new QSslSocket(); if (use_ssl) { connect(usenet, SIGNAL(encrypted()), this, SLOT(gotConnected())); } else { connect(usenet, SIGNAL(connected()), this, SLOT(gotConnected())); } connect(usenet, SIGNAL(readyRead()), this, SLOT(gotData())); connect(usenet, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(gotError(QAbstractSocket::SocketError))); initialize(((MainWindow*)parent)->getConfig()->value("server/host").toString(), ((MainWindow*)parent)->getConfig()->value("server/port").toInt()); last_time = QTime::currentTime(); last_bytes = 0; total_bytes = 0; /*QTimer *speedTimer = new QTimer(this); connect(speedTimer, SIGNAL(timeout()), this, SLOT(speedMonitor())); speedTimer->start(5000);*/ connect(&speedTimer, SIGNAL(timeout()), this, SLOT(speedMonitor())); speedTimer.start(5000); } void Downloader::processFiles() { if (downloading) return; if (paused) { speedTimer.stop(); terminate(); dldata.clear(); //delete usenet; Crashes Qt emit downloadEvent("0", thread_id, 4); emit downloadEvent("Paused", thread_id, 6); this->exit(); return; } if (getNext()) { downloading = true; if (!last_failed) emit downloadEvent("Downloading ["+QString::number(file+1)+": "+QString::number(seg+1)+"/"+QString::number(nzblist->getFile(file)->getSegments()->size())+"] "+nzblist->getFile(file)->getSegment(seg)->getMsgid(), thread_id); getArticle(nzblist->getFile(file)->getSegment(seg)->getMsgid()); } } bool Downloader::getNext() { file_lock->lock(); for (;;) { if (nzblist->getFile(file)->getSegment(seg)->getStatus() == NZB_NONE && l->item(file, 0)->checkState() == Qt::Checked) { nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_DOWNLOADING); file_lock->unlock(); return true; } if (nzblist->getFile(file)->getSegment(seg)->getStatus() == NZB_NONE && l->item(file, 0)->checkState() == Qt::Unchecked) { nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_DL_SKIPPED); file_lock->unlock(); emit downloadEvent("", file, 2); emit processNext(); return false; } if (nzblist->getFile(file)->getSegments()->size() == seg+1) { if (nzblist->getList()->size() == file+1) { file_lock->unlock(); emit downloadEvent("Disconnecting", thread_id); terminate(); emit downloadEvent("Disconnected", thread_id); return false; } file = file + 1; seg = 0; } else { seg = seg + 1; } } return false; } void Downloader::stop() { speedTimer.stop(); emit downloadEvent("0", thread_id, 4); emit downloadEvent("Disconnecting", thread_id); terminate(); emit downloadEvent("Disconnected", thread_id); dldata.clear(); //delete usenet; this->exit(); } void Downloader::pause() { paused = true; } void Downloader::resume() { // This slot is not used/supported. paused = false; emit processNext(); } void Downloader::gotError(QAbstractSocket::SocketError err) { speedTimer.stop(); emit downloadEvent("0", thread_id, 4); nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_NONE); usenet->close(); ((MainWindow*)parent)->getDownloaderItem(thread_id)->setIcon(QIcon(":/images/connect_no.png")); dldata.clear(); //delete usenet; causes crash in Qt emit downloadEvent("Connection error, reconnecting in 1 minute...", thread_id, 5); this->exit(); } void Downloader::initialize(QString host, int port) { if (use_ssl) { usenet->connectToHostEncrypted(host, port); } else { usenet->connectToHost(host, port); } } void Downloader::gotConnected() { ((MainWindow*)parent)->getDownloaderItem(thread_id)->setIcon(QIcon(":/images/connect_established.png")); } void Downloader::terminate() { if (usenet->ConnectedState == QAbstractSocket::ConnectedState) { usenet->write(((QString)("QUIT\r\n")).toAscii()); usenet->flush(); } usenet->waitForReadyRead(); usenet->close(); usenet->waitForDisconnected(); ((MainWindow*)parent)->getDownloaderItem(thread_id)->setIcon(QIcon(":/images/connect_no.png")); } void Downloader::getArticle(QString msgid) { usenet->write(("ARTICLE "+msgid+"\r\n").toAscii()); usenet->flush(); dldata.clear(); } void Downloader::gotData() { QIODevice *buffer; buffer = usenet; if (!buffer->bytesAvailable()) { qDebug("gotData() empty."); return; } total_bytes += buffer->bytesAvailable(); QByteArray hdata; int pos; if (dldata.isEmpty()) { hdata = buffer->readLine(); if (hdata.left(3) == "200" || hdata.left(3) == "201") { if (((MainWindow*)parent)->getConfig()->value("server/auth").toBool()) { buffer->write(("AUTHINFO USER "+((MainWindow*)parent)->getConfig()->value("server/username").toString()+"\r\n").toAscii()); ((QSslSocket*)buffer)->flush(); return; } else { emit downloadEvent("Connected", thread_id, 3); emit processNext(); return; } } else if (hdata.left(3) == "381") { buffer->write(("AUTHINFO PASS "+((MainWindow*)parent)->getConfig()->value("server/password").toString()+"\r\n").toAscii()); ((QSslSocket*)buffer)->flush(); return; } else if (hdata.left(3) == "281") { qDebug("Connected"); emit downloadEvent("Connected", thread_id, 3); emit processNext(); return; } else if (hdata.left(1) == "4" || hdata.left(1) == "5") { last_failed = true; emit downloadEvent("NNTP Error: "+hdata, thread_id); emit downloadEvent("", file, 2); qDebug() << "Error: " << hdata; nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_DL_SKIPPED); downloading = false; emit processNext(); return; } else if (hdata.left(2) == "22") { dldata = hdata; dldata += buffer->readAll(); } else { /*emit downloadEvent("Connect failure", thread_id); qDebug("Connection failed."); nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_DOWNLOADED); buffer->close(); qDebug("NNTP Protocol handshake error."); qDebug() << "Data: " << hdata; return;*/ dldata = hdata+buffer->readAll(); } } else { dldata += buffer->readAll(); } if (dldata.right(5) == "\r\n.\r\n") { last_failed = false; downloading = false; dldata = dldata.left(dldata.length() - 3); pos = dldata.indexOf("\r\n\r\n", 0); dldata = dldata.mid(pos + 4, dldata.length() - (pos + 4)); if (dldata.left(2) == "..") { dldata.remove(0, 1); } pos = 0; while ((pos = dldata.indexOf("\r\n..", pos+3)) != -1) { dldata.remove(pos + 2, 1); } nzblist->getFile(file)->getSegment(seg)->setData(dldata); nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_DOWNLOADED); //dldata.clear(); emit downloadEvent("", file, 2); emit processNext(); } } void Downloader::speedMonitor() { if (int secs = last_time.secsTo(QTime::currentTime())) { double speed = ((total_bytes - last_bytes) / secs) / 1000; last_time = QTime::currentTime(); if (total_bytes == last_bytes) { same_bytes++; } else { same_bytes = 0; } last_bytes = total_bytes; if (same_bytes >= 24) { // Two minutes idle same_bytes = 0; speedTimer.stop(); nzblist->getFile(file)->getSegment(seg)->setStatus(NZB_NONE); usenet->close(); ((MainWindow*)parent)->getDownloaderItem(thread_id)->setIcon(QIcon(":/images/connect_no.png")); dldata.clear(); delete usenet; emit downloadEvent("Connection idle for too long, reconnecting in 1 minute...", thread_id, 5); this->exit(); } emit downloadEvent(QString::number(speed), thread_id, 4); } } nzb-0.2/src/nzbdata.cpp0000644000175000017500000001043311212526255015366 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: nzbdata.cpp,v 1.6 2005/09/25 11:23:26 mnordstr Exp $ * This file provides the nzb data classes. */ #include "nzbdata.h" void NzbList::importNzb(QFile *nzbfile) { QXmlSimpleReader xmlReader; QXmlInputSource *source = new QXmlInputSource(nzbfile); NzbHandler *handler = new NzbHandler(this, nzbfile->fileName()); xmlReader.setContentHandler(handler); xmlReader.setErrorHandler(handler); xmlReader.parse(source); } void NzbList::sortList() { qSort(files.begin(), files.end(), NzbFile::subjectLessThan); } int NzbList::totalParts() { int parts = 0; for (int i=0; isize(); } return parts; } qint64 NzbList::totalSize() { qint64 size = Q_INT64_C(0); for (int i=0; iposter = poster; this->date = date; this->subject = subject; this->bytes = 0; this->encoding = NZB_UNDEF; } void NzbFile::addSegment(NzbSeg newseg) { segments.append(newseg); bytes += newseg.getBytes(); } bool NzbFile::subjectLessThan(const NzbFile &nf1, const NzbFile &nf2) { // Put .rar before e.g. .r00 QRegExp rar("*.rar*"); rar.setPatternSyntax(QRegExp::Wildcard); rar.setCaseSensitivity(Qt::CaseInsensitive); QRegExp rxx(".*[.][rR]\\d+.*"); if (rar.exactMatch(nf1.subject) && nf2.subject.contains(rxx)) { return true; } else if (rar.exactMatch(nf2.subject) && nf1.subject.contains(rxx)) { return false; } return (nf1.subject < nf2.subject); } NzbSeg::NzbSeg(int bytes, QString msgid) { this->bytes = bytes; this->msgid = msgid; this->status = NZB_NONE; } NzbHandler::NzbHandler(NzbList *parent, QString nzbfile) { metNzbTag = false; this->parent = parent; nzbFileName = nzbfile; } bool NzbHandler::startElement(const QString &, const QString &, const QString &qName, const QXmlAttributes &attributes) { if (!metNzbTag && qName != "nzb") { errorStr = "The file is not an NZB file."; return false; } if (qName == "nzb") { metNzbTag = true; } else if (qName == "file") { QDateTime epoch; epoch.setTime_t((attributes.value("date")).toInt()); NzbFile newfile(attributes.value("poster"), epoch, attributes.value("subject")); currentFile = newfile; fileSegs.clear(); } else if (qName == "segment") { currentBytes = attributes.value("bytes").toInt(); currentSegPos = attributes.value("number").toInt(); } currentText.clear(); return true; } bool NzbHandler::endElement(const QString &, const QString &, const QString &qName) { if (qName == "group") { currentFile.addGroup(currentText); } else if (qName == "segment") { NzbSeg newseg(currentBytes, "<"+currentText+">"); //currentFile.addSegment(newseg, currentSegPos); fileSegs[currentSegPos] = newseg; currentFile.setNzbFileName(nzbFileName); } else if (qName == "file") { for (int i = 1; i <= fileSegs.size(); i++) { currentFile.addSegment(fileSegs.value(i)); } parent->addFile(currentFile); } return true; } bool NzbHandler::characters(const QString &str) { currentText += str; return true; } bool NzbHandler::fatalError(const QXmlParseException &exception) { QMessageBox::information(NULL, "nzb", QObject::tr("Parse error at line %1, column %2:\n%3").arg(exception.lineNumber()).arg(exception.columnNumber()).arg(exception.message())); return false; } QString NzbHandler::errorString() const { return errorStr; } nzb-0.2/src/output.h0000644000175000017500000000363611212526255014757 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: output.h,v 1.3 2005/09/08 21:43:33 mnordstr Exp $ * This file provides the output functions. */ #ifndef OUTPUT_H #define OUTPUT_H #include #include #include "nzbdata.h" class DetachedPlayer; class Output : public QThread { Q_OBJECT public: Output(NzbList *nzblist, int thread_id, QMutex *file_lock, QObject *parent = 0); void run(); void setStream(bool stream) { this->stream = stream; } signals: void outputEvent(QString message, int type = 0); public slots: void process(); void stop(); private slots: void newConnection(); void closeStream(); private: bool getNext(int *file, int *seg); void writeOut(int file, int seg); bool stream, first_output; QFile *fout; QObject *parent; NzbList *nzblist; int thread_id; QMutex *file_lock; int file, seg, last_file; QString fn; QTcpServer *streamer; QTcpSocket *ms; QProcess *mp; DetachedPlayer *player; }; class DetachedPlayer : public QThread { Q_OBJECT public: DetachedPlayer(QString cmd) { this->cmd = cmd; } void run(); private: QString cmd; }; #endif nzb-0.2/src/nzb.rc0000644000175000017500000000010410512232702014341 0ustar mnordstrmnordstrIDI_ICON1 ICON DISCARDABLE "../images/nzb.ico" nzb-0.2/src/nzb.qrc0000644000175000017500000000104611731364766014554 0ustar mnordstrmnordstr ../images/open.png ../images/nzb.png ../images/start.png ../images/stop.png ../images/stream.png ../images/decoder.png ../images/encrypted.png ../images/connect_creating.png ../images/connect_established.png ../images/connect_no.png ../images/multimedia2.png ../images/player_pause.png nzb-0.2/src/mainwindow.cpp0000644000175000017500000005613111731653477016141 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: mainwindow.cpp,v 1.18 2005/10/16 12:03:32 mnordstr Exp $ * This file provides the GUI. */ #include #include "mainwindow.h" #include "downloader.h" MainWindow::MainWindow() { shutting_down = false; download_complete = false; split = new QSplitter(Qt::Vertical, this); l = new QTableWidget(split); l->setColumnCount(4); l->setHorizontalHeaderLabels((QStringList)"Subject" << "Progress" << "Parts" << "Size"); l->setSelectionMode(QAbstractItemView::NoSelection); l->setSelectionBehavior(QAbstractItemView::SelectRows); l->setShowGrid(false); l->verticalHeader()->hide(); d = new QTableWidget(split); d->setColumnCount(3); d->setHorizontalHeaderLabels((QStringList)"Thread" << "Status" << "Speed"); d->setSelectionMode(QAbstractItemView::NoSelection); d->setSelectionBehavior(QAbstractItemView::SelectRows); d->setShowGrid(false); d->verticalHeader()->hide(); setCentralWidget(split); createActions(); createMenus(); createToolBars(); createStatusBar(); readSettings(); d->setRowCount(config->value("server/connections").toInt() + 1); int i; for (i=0; ivalue("server/connections").toInt(); i++) { downloaders.append(new Downloader(&nzblist, i, &file_lock, l, this)); d->setItem(i, 0, new QTableWidgetItem(QIcon(":/images/connect_no.png"), "Downloader #"+QString::number(i+1))); d->setItem(i, 2, new QTableWidgetItem("0 kB/s")); d->item(i, 2)->setTextAlignment(Qt::AlignRight); connect(this, SIGNAL(fullStop()), downloaders[i], SLOT(stop())); connect(this, SIGNAL(requestPause()), downloaders[i], SLOT(pause())); connect(this, SIGNAL(requestResume()), downloaders[i], SLOT(resume())); } decoders.append(new Decoder(&nzblist, i, &file_lock, this)); d->setItem(i, 0, new QTableWidgetItem(QIcon(":/images/decoder.png"), "Decoder #1")); connect(this, SIGNAL(processDecoder()), decoders[0], SLOT(processFiles())); connect(this, SIGNAL(fullStop()), decoders[0], SLOT(stop())); output = new Output(&nzblist, i+1, &file_lock, this); connect(this, SIGNAL(processOutput()), output, SLOT(process())); connect(this, SIGNAL(fullStop()), output, SLOT(stop())); int drows = d->rowCount(); for (int row=0; rowsetRowHeight(row, 16); } //d->resizeRowsToContents(); setWindowTitle(tr("nzb")); } void MainWindow::closeEvent(QCloseEvent *event) { shutting_down = true; if (stopAct->isEnabled()) { stop(); if (!shutting_down) { event->ignore(); return; } } writeSettings(); event->accept(); } void MainWindow::open() { QString fileName = QFileDialog::getOpenFileName(this, "Open NZB File", NULL, "nzb Files (*.nzb)\nAll Files (*)"); if (!fileName.isEmpty()) loadFile(fileName); } void MainWindow::about() { QMessageBox::about(this, tr("About nzb"), tr("nzb v%1\n\nnzb is a Usenet binary downloader.\n\nSee http://www.nzb.fi/").arg(NZB_VERSION)); } void MainWindow::createActions() { openAct = new QAction(QIcon(":/images/open.png"), tr("&Open..."), this); openAct->setShortcut(tr("Ctrl+O")); openAct->setStatusTip(tr("Open a file")); connect(openAct, SIGNAL(triggered()), this, SLOT(open())); exitAct = new QAction(tr("E&xit"), this); exitAct->setShortcut(tr("Ctrl+Q")); exitAct->setStatusTip(tr("Exit the application")); connect(exitAct, SIGNAL(triggered()), this, SLOT(close())); sortAct = new QAction(tr("&Sort"), this); sortAct->setStatusTip(tr("Sort the nzb list")); connect(sortAct, SIGNAL(triggered()), this, SLOT(sortList())); clearAct = new QAction(tr("&Clear"), this); clearAct->setStatusTip(tr("Clear the nzb list")); connect(clearAct, SIGNAL(triggered()), this, SLOT(clearList())); aboutAct = new QAction(tr("&About"), this); aboutAct->setStatusTip(tr("Show the application's About box")); connect(aboutAct, SIGNAL(triggered()), this, SLOT(about())); optionsAct = new QAction(tr("&Options"), this); optionsAct->setStatusTip(tr("Show the options dialog")); connect(optionsAct, SIGNAL(triggered()), this, SLOT(options())); startAct = new QAction(QIcon(":/images/start.png"), tr("&Start"), this); startAct->setShortcut(tr("Ctrl+S")); startAct->setStatusTip(tr("Start downloader")); startAct->setEnabled(false); connect(startAct, SIGNAL(triggered()), this, SLOT(start())); stopAct = new QAction(QIcon(":/images/stop.png"), tr("S&top"), this); stopAct->setShortcut(tr("Ctrl+T")); stopAct->setStatusTip(tr("Stop downloader")); stopAct->setEnabled(false); connect(stopAct, SIGNAL(triggered()), this, SLOT(stop())); streamAct = new QAction(QIcon(":/images/multimedia2.png"), tr("St&ream"), this); streamAct->setShortcut(tr("Ctrl+R")); streamAct->setStatusTip(tr("Start streaming")); streamAct->setEnabled(false); connect(streamAct, SIGNAL(triggered()), this, SLOT(stream())); pauseAct = new QAction(QIcon(":/images/player_pause.png"), tr("&Pause"), this); pauseAct->setShortcut(tr("Ctrl+P")); pauseAct->setStatusTip(tr("Pause")); pauseAct->setEnabled(false); connect(pauseAct, SIGNAL(triggered()), this, SLOT(pause())); resumeAct = new QAction(QIcon(":/images/stream.png"), tr("Resum&e"), this); resumeAct->setShortcut(tr("Ctrl+E")); resumeAct->setStatusTip(tr("Resume")); resumeAct->setEnabled(false); connect(resumeAct, SIGNAL(triggered()), this, SLOT(resume())); } void MainWindow::createMenus() { fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(openAct); fileMenu->addSeparator(); fileMenu->addAction(exitAct); menuBar()->addSeparator(); editMenu = menuBar()->addMenu(tr("&Edit")); editMenu->addAction(sortAct); editMenu->addAction(clearAct); menuBar()->addSeparator(); actionsMenu = menuBar()->addMenu(tr("&Actions")); actionsMenu->addAction(startAct); actionsMenu->addAction(stopAct); actionsMenu->addAction(streamAct); actionsMenu->addAction(pauseAct); actionsMenu->addAction(resumeAct); menuBar()->addSeparator(); toolsMenu = menuBar()->addMenu(tr("&Tools")); toolsMenu->addAction(optionsAct); menuBar()->addSeparator(); helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(aboutAct); } void MainWindow::createToolBars() { fileToolBar = addToolBar(tr("File")); fileToolBar->addAction(openAct); actionToolBar = addToolBar(tr("Actions")); actionToolBar->addAction(startAct); actionToolBar->addAction(stopAct); actionToolBar->addAction(streamAct); actionToolBar->addAction(pauseAct); actionToolBar->addAction(resumeAct); } void MainWindow::createStatusBar() { statusBar()->showMessage(tr("Ready")); totalSpeed = new QLabel("0 kB/s"); totalSize = new QLabel("0 MB"); totalProgress = new QProgressBar(); totalProgress->setRange(0, 1); totalProgress->setValue(0); statusBar()->addPermanentWidget(totalSpeed); statusBar()->addPermanentWidget(totalProgress); statusBar()->addPermanentWidget(totalSize); } void MainWindow::readSettings() { if (QFile::exists("nzb.ini")) { config = new QSettings("nzb.ini", QSettings::IniFormat); } else { config = new QSettings("nzb", "nzb"); } QPoint pos = config->value("mainwindow/pos", QPoint(200, 200)).toPoint(); QSize size = config->value("mainwindow/size", QSize(400, 400)).toSize(); resize(size); move(pos); split->restoreState(config->value("mainwindow/split").toByteArray()); l->horizontalHeader()->resizeSection(0, config->value("mainwindow/listcol0width", 50).toInt()); l->horizontalHeader()->resizeSection(1, config->value("mainwindow/listcol1width", 50).toInt()); l->horizontalHeader()->resizeSection(2, config->value("mainwindow/listcol2width", 50).toInt()); l->horizontalHeader()->resizeSection(3, config->value("mainwindow/listcol3width", 50).toInt()); d->horizontalHeader()->resizeSection(0, config->value("mainwindow/dlistcol0width", 50).toInt()); d->horizontalHeader()->resizeSection(1, config->value("mainwindow/dlistcol1width", 50).toInt()); d->horizontalHeader()->resizeSection(2, config->value("mainwindow/dlistcol2width", 50).toInt()); // Fix for situation when the default value isn't honored. if (l->columnWidth(0) == 0) l->horizontalHeader()->resizeSection(0, 50); if (l->columnWidth(1) == 0) l->horizontalHeader()->resizeSection(1, 50); if (l->columnWidth(2) == 0) l->horizontalHeader()->resizeSection(2, 50); if (l->columnWidth(3) == 0) l->horizontalHeader()->resizeSection(3, 50); if (d->columnWidth(0) == 0) d->horizontalHeader()->resizeSection(0, 50); if (d->columnWidth(1) == 0) d->horizontalHeader()->resizeSection(1, 50); if (d->columnWidth(2) == 0) d->horizontalHeader()->resizeSection(2, 50); } void MainWindow::writeSettings() { config->setValue("mainwindow/pos", pos()); config->setValue("mainwindow/size", size()); config->setValue("mainwindow/split", split->saveState()); config->setValue("mainwindow/listcol0width", l->columnWidth(0)); config->setValue("mainwindow/listcol1width", l->columnWidth(1)); config->setValue("mainwindow/listcol2width", l->columnWidth(2)); config->setValue("mainwindow/listcol3width", l->columnWidth(3)); config->setValue("mainwindow/dlistcol0width", d->columnWidth(0)); config->setValue("mainwindow/dlistcol1width", d->columnWidth(1)); config->setValue("mainwindow/dlistcol2width", d->columnWidth(2)); config->sync(); } void MainWindow::populateList() { int i; int row = l->rowCount(); l->setRowCount(nzblist.getList()->count()); for (i=row; irowCount(); i++) { QTableWidgetItem *newItem = new QTableWidgetItem(nzblist.getFile(i)->getSubject()); QTableWidgetItem *newItem_prog = new QTableWidgetItem("0"); QTableWidgetItem *newItem_tot = new QTableWidgetItem(QString::number(nzblist.getFile(i)->getSegments()->size())); QTableWidgetItem *newItem_size; int file_bytes = nzblist.getFile(i)->getBytes(); /*if (file_bytes > 999999999) { newItem_size = new QTableWidgetItem(QString::number((file_bytes / 1073741824))+" GB"); } else*/ if (file_bytes > 999999) { newItem_size = new QTableWidgetItem(QString::number((file_bytes / 1048576))+" MB"); } else if (file_bytes > 999) { newItem_size = new QTableWidgetItem(QString::number((file_bytes / 1024))+" kB"); } else { newItem_size = new QTableWidgetItem(QString::number(file_bytes)+" bytes"); } newItem->setCheckState(Qt::Checked); l->setItem(row, 0, newItem); l->setItem(row, 1, newItem_prog); l->setItem(row, 2, newItem_tot); l->setItem(row, 3, newItem_size); newItem_prog->setTextAlignment(Qt::AlignRight); newItem_tot->setTextAlignment(Qt::AlignRight); newItem_size->setTextAlignment(Qt::AlignRight); if (config->value("list/par_uncheck").toBool()) { QRegExp rx("*vol*.par2"); rx.setPatternSyntax(QRegExp::Wildcard); rx.setCaseSensitivity(Qt::CaseInsensitive); if (nzblist.getFile(i)->getSubject().contains(rx)) { newItem->setCheckState(Qt::Unchecked); } } //l->resizeRowToContents(row); l->setRowHeight(row, 16); row++; } qint64 total_size = nzblist.totalSize(); if (total_size > 999999999) { totalSize->setText(QString::number((double)((double)(total_size / 1048576) / 1000), 'f', 2)+" GB"); } else if (total_size > 999999) { totalSize->setText(QString::number((total_size / 1048576))+" MB"); } else if (total_size > 999) { totalSize->setText(QString::number((total_size / 1024))+" kB"); } else { totalSize->setText(QString::number(total_size)+" bytes"); } totalProgress->setRange(0, nzblist.totalParts()); } void MainWindow::loadFile(const QString &fileName) { QFile f(fileName); nzblist.importNzb(&f); if (config->value("list/autosort").toBool()) { sortList(); } else { populateList(); } if (nzblist.getList()->count()) { startAct->setEnabled(true); streamAct->setEnabled(true); } statusBar()->showMessage( tr("Loaded nzb document %1").arg(fileName), 2000 ); } void MainWindow::sortList() { l->clear(); l->setRowCount(0); l->setHorizontalHeaderLabels((QStringList)"Subject" << "Progress" << "Parts" << "Size"); nzblist.sortList(); populateList(); } void MainWindow::start() { download_complete = false; startAct->setEnabled(false); stopAct->setEnabled(true); streamAct->setEnabled(false); pauseAct->setEnabled(true); resumeAct->setEnabled(false); for (int i=0; istart(QThread::LowPriority); } decoders[0]->start(); output->setStream(false); output->start(QThread::TimeCriticalPriority); } void MainWindow::restartDownloaders() { for (int i=0; iisFinished()) { downloaders[i]->start(QThread::LowPriority); } } } void MainWindow::stop() { if (!download_complete) { QMessageBox msgBox; msgBox.setText("If you stop the current file list will be cleared. You can use the pause action to temporary stop the download."); msgBox.setInformativeText("Are you sure you want to stop and clear the list?"); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); msgBox.setIcon(QMessageBox::Question); int ret = msgBox.exec(); if (ret == QMessageBox::No) { shutting_down = false; return; } } startAct->setEnabled(true); stopAct->setEnabled(false); streamAct->setEnabled(true); pauseAct->setEnabled(false); resumeAct->setEnabled(false); qDebug("Stopping"); emit fullStop(); for (int i=0; iwait(); } decoders[0]->wait(); //output->wait(); clearData(); setWindowTitle(tr("nzb")); } void MainWindow::clearData() { l->clear(); l->setRowCount(0); l->setHorizontalHeaderLabels((QStringList)"Subject" << "Progress" << "Parts" << "Size"); nzblist.clearList(); totalSize->setText("0 MB"); totalProgress->setRange(0,1); totalProgress->setValue(0); startAct->setEnabled(false); streamAct->setEnabled(false); pauseAct->setEnabled(false); resumeAct->setEnabled(false); } void MainWindow::stream() { if (getConfig()->value("output/mediaplayer").toString().isEmpty()) { QMessageBox msgBox; msgBox.setText("No media player has been specified in output settings."); msgBox.setInformativeText("Please specify an application for streaming before using this function."); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Critical); msgBox.exec(); return; } startAct->setEnabled(false); stopAct->setEnabled(true); streamAct->setEnabled(false); pauseAct->setEnabled(true); resumeAct->setEnabled(false); for (int i=0; istart(QThread::LowPriority); } decoders[0]->start(); output->setStream(true); output->start(QThread::TimeCriticalPriority); } void MainWindow::pause() { startAct->setEnabled(false); stopAct->setEnabled(true); streamAct->setEnabled(false); pauseAct->setEnabled(false); resumeAct->setEnabled(true); emit requestPause(); } void MainWindow::resume() { startAct->setEnabled(false); stopAct->setEnabled(true); streamAct->setEnabled(false); pauseAct->setEnabled(true); resumeAct->setEnabled(false); emit restartDownloaders(); } void MainWindow::options() { OptionsDlg *dl = new OptionsDlg(this); dl->exec(); } void MainWindow::downloadEvent(QString message, int row, int type) { if (type == 0) { d->model()->setData(d->model()->index(row, 1), message); } else if (type == 2) { l->model()->setData(l->model()->index(row, 1), (l->item(row, 1)->text()).toInt()+1); totalProgress->setValue(totalProgress->value() + 1); setWindowTitle(tr("nzb") + " - " + QString::number(((double)totalProgress->value() / (double)totalProgress->maximum() * 100.00), 'f' , 0) + "%"); if (l->item(row, 1)->text() == "1") { if (config->value("list/hidecompleted").toBool()) { l->scrollToItem(l->item(row, 1)); } } emit processDecoder(); } else if (type == 3) { d->model()->setData(d->model()->index(row, 1), message); } else if (type == 4) { d->model()->setData(d->model()->index(row, 2), message+" kB/s"); int total_speed = 0; for (int i=0; iitem(i, 2)->text(); total_speed += cur_speed.mid(0, cur_speed.length() - 5).toInt(); } totalSpeed->setText(QString::number(total_speed)+" kB/s"); } else if (type == 5) { d->model()->setData(d->model()->index(row, 1), message); QTimer::singleShot(60000, this, SLOT(restartDownloaders())); } else if (type == 6) { d->model()->setData(d->model()->index(row, 1), message); } } void MainWindow::decodeEvent(QString message, int row, int type) { d->model()->setData(d->model()->index(row, 1), message); if (type == 1) { emit processOutput(); } else if (type == 2) { QTimer::singleShot(1000, decoders[0], SLOT(processFiles())); } else if (type == 3) { QTimer::singleShot(0, output, SLOT(process())); } } void MainWindow::outputEvent(QString message, int type) { if (type == 1) { download_complete = true; QTimer::singleShot(200, this, SLOT(stop())); } else if (type == 2) { download_complete = true; QTimer::singleShot(200, this, SLOT(stop())); QMessageBox *msgbox = new QMessageBox("nzb Error", message, QMessageBox::Critical, QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); msgbox->show(); } } OptionsDlg::OptionsDlg(QWidget *parent) : QDialog(parent) { ui.setupUi(this); this->parent = parent; connect(ui.okButton, SIGNAL(clicked()), this, SLOT(accept())); connect(ui.cancelButton, SIGNAL(clicked()), this, SLOT(reject())); connect(ui.saveBrowse, SIGNAL(clicked()), this, SLOT(browseSave())); connect(ui.mediaBrowse, SIGNAL(clicked()), this, SLOT(browseMedia())); connect(ui.subfolders, SIGNAL(stateChanged(int)), this, SLOT(enableGuess(int))); connect(ui.use_file, SIGNAL(clicked()), this, SLOT(useFile())); QSettings *config = ((MainWindow*)parent)->getConfig(); if (QFile::exists("nzb.ini")) { ui.use_file->setEnabled(false); } #ifndef Q_WS_WIN ui.use_file->setEnabled(false); #endif ui.host->setText(config->value("server/host").toString()); ui.port->setText(config->value("server/port").toString()); ui.username->setText(config->value("server/username").toString()); ui.password->setText(config->value("server/password").toString()); ui.connections->setValue(config->value("server/connections").toInt()); if (config->value("server/auth").toBool()) { ui.auth->setCheckState(Qt::Checked); } else { ui.auth->setCheckState(Qt::Unchecked); } if (config->value("server/ssl").toBool()) { ui.ssl->setCheckState(Qt::Checked); } else { ui.ssl->setCheckState(Qt::Unchecked); } if (config->value("list/autosort").toBool()) { ui.autosort->setCheckState(Qt::Checked); } else { ui.autosort->setCheckState(Qt::Unchecked); } if (config->value("list/hidecompleted").toBool()) { ui.hidecompleted->setCheckState(Qt::Checked); } else { ui.hidecompleted->setCheckState(Qt::Unchecked); } if (config->value("list/par_uncheck").toBool()) { ui.par_uncheck->setCheckState(Qt::Checked); } else { ui.par_uncheck->setCheckState(Qt::Unchecked); } ui.savepath->setText(config->value("output/savepath").toString()); if (config->value("output/subfolders").toBool()) { ui.subfolders->setCheckState(Qt::Checked); ui.guessalbum->setEnabled(true); } else { ui.subfolders->setCheckState(Qt::Unchecked); ui.guessalbum->setEnabled(false); } if (config->value("output/guessalbum").toBool()) { ui.guessalbum->setCheckState(Qt::Checked); } else { ui.guessalbum->setCheckState(Qt::Unchecked); } ui.mediaplayer->setText(config->value("output/mediaplayer").toString()); if (config->value("output/stream").toBool()) { ui.stream->setCheckState(Qt::Checked); } else { ui.stream->setCheckState(Qt::Unchecked); } connect(ui.ssl, SIGNAL(stateChanged(int)), this, SLOT(sslChange(int))); } void OptionsDlg::accept() { QSettings *config = ((MainWindow*)parent)->getConfig(); config->setValue("server/host", ui.host->text()); config->setValue("server/port", ui.port->text().toInt()); config->setValue("server/username", ui.username->text()); config->setValue("server/password", ui.password->text()); config->setValue("server/connections", ui.connections->value()); config->setValue("server/auth", ((ui.auth->checkState() == Qt::Checked) ? true : false)); config->setValue("server/ssl", ((ui.ssl->checkState() == Qt::Checked) ? true : false)); config->setValue("list/autosort", ((ui.autosort->checkState() == Qt::Checked) ? true : false)); config->setValue("list/hidecompleted", ((ui.hidecompleted->checkState() == Qt::Checked) ? true : false)); config->setValue("list/par_uncheck", ((ui.par_uncheck->checkState() == Qt::Checked) ? true : false)); config->setValue("output/savepath", ui.savepath->text()); config->setValue("output/subfolders", ((ui.subfolders->checkState() == Qt::Checked) ? true : false)); config->setValue("output/guessalbum", ((ui.guessalbum->checkState() == Qt::Checked) ? true : false)); config->setValue("output/mediaplayer", ui.mediaplayer->text()); config->setValue("output/stream", ((ui.stream->checkState() == Qt::Checked) ? true : false)); config->sync(); QDialog::accept(); } void OptionsDlg::reject() { QDialog::reject(); } void OptionsDlg::browseSave() { QString dir = QFileDialog::getExistingDirectory(this, "Choose a save directory", "", QFileDialog::DontResolveSymlinks); if (dir != "") { ui.savepath->setText(dir); } } void OptionsDlg::browseMedia() { #ifdef WIN32 QString dir = QFileDialog::getOpenFileName(this, "Choose media player", "", "Executables (*.exe);;All Files (*)"); #else QString dir = QFileDialog::getOpenFileName(this, "Choose media player", "", "All Files (*)"); #endif if (dir != "") { ui.mediaplayer->setText("\""+dir+"\""); } } void OptionsDlg::enableGuess(int state) { if (state == Qt::Checked) { ui.guessalbum->setEnabled(true); } else { ui.guessalbum->setEnabled(false); } } void OptionsDlg::sslChange(int state) { if (state == Qt::Checked) { ui.port->setText("563"); } else { ui.port->setText("119"); } } void OptionsDlg::useFile() { QSettings *config = new QSettings("nzb.ini", QSettings::IniFormat); ((MainWindow*)parent)->setConfig(config); config->sync(); ui.use_file->setEnabled(false); } nzb-0.2/src/downloader.h0000644000175000017500000000411511425262734015553 0ustar mnordstrmnordstr/* * nzb * * Copyright (C) 2004-2006 Mattias Nordstrom * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Authors: * Mattias Nordstrom * * $Id: downloader.h,v 1.3 2005/10/15 17:45:36 mnordstr Exp $ * This file provides the NNTP downloader. */ #ifndef DOWNLOADER_H #define DOWNLOADER_H #include #include "nzbdata.h" class Downloader : public QThread { Q_OBJECT public: Downloader(NzbList *nzblist, int thread_id, QMutex *file_lock, QTableWidget *l, QObject *parent = 0); void run(); signals: void downloadEvent(QString message, int row, int type = 0); void processNext(); public slots: void processFiles(); void stop(); void pause(); void resume(); private slots: void init(); void gotData(); void speedMonitor(); void gotConnected(); void gotError(QAbstractSocket::SocketError err); //void ssl_readyRead(); //void ssl_outgoingReady(); private: void initialize(QString host, int port); void terminate(); void getArticle(QString msgid); bool getNext(); QObject *parent; QSslSocket *usenet; NzbList *nzblist; QTableWidget *l; QMutex *file_lock; int thread_id; int file, seg; QByteArray dldata; bool downloading, paused; QTime last_time; int total_bytes; int last_bytes; int same_bytes; //SSLFilter ssl; bool use_ssl; bool last_failed; QTimer speedTimer; }; #endif nzb-0.2/README0000644000175000017500000000031510521132603013316 0ustar mnordstrmnordstrnzb === Requirements - Qt 4 Instructions 1. Run qmake 2. Run make nzb executable in bin/nzb Compilation requires OpenSSL headers, on Windows you should put the openssl includes folder under src. nzb-0.2/images/0000755000175000017500000000000011731653621013720 5ustar mnordstrmnordstrnzb-0.2/images/connect_established.png0000644000175000017500000000114610521115120020407 0ustar mnordstrmnordstrPNG  IHDRabKGDAx= pHYs  tIME  /IDATx1A6`a5ϙڷ `k aiIENDB`nzb-0.2/images/encrypted.png0000644000175000017500000000123110521115120016377 0ustar mnordstrmnordstrPNG  IHDR/\bKGD pHYs  tIME("&IDATx͓Okaw&Ė&ZC/R=D5ipFzqvR&S823-i?AR ԬeY uC {|qkyfVVnp}|<(xn7ũ-ZR?lY}R k-RJ·a|gc u],cg: p}߽}P׵?}. EBiB)E۶mRiBE!cP%۶-\)actV \a~a3߾oV1~dIENDB`nzb-0.2/images/connect_no.png0000644000175000017500000000120010521115120016523 0ustar mnordstrmnordstrPNG  IHDRabKGDAx= pHYs  s tIME  x8 IDATx}?kZQW./^w8)7HkW \3HrΔAP *C.bM*ʽ.Ly~LYJTz54=yeYrf0_h*h4( 8C<'ͪ./`t TV9>>~8h4B>) DAdb6n!iV/y'`2X0ZF¶m9*3D"$h4mT*E\VX`::t:t:}!^E%zM$n(I)52y(۶Y,7_a\]]eYִc]|ZI<)}oHCxwLUvԨϤ 9nnJʟJ,:%Bqoxggqڠ9w{{dN8 ?> ?k^0 FnWN@åTvm#>j FQ0m rLs\ȞJսR@I(R]e}ޖ!! 0 S7n|S}J~wu9ҺxޮO&#oxF e'z >z?@LB}$RINmg$:(xWeHФzk(0(}P<$m,c`! !`unv!ܓw]Beg=ꃵZ \ ϟGReY0M utm& qh%`\z=V»¾} v qPWMa JBP[)_j=urY\xO,huze#y+(4Y߬޻6%uPT*E8~8^bUUitS֪EZ(,tOg.'D'I!N?x<f$as+ ]>qR3s_D^_lrmePƉbtT=H|I._ e t@P }12_ԢIENDB`nzb-0.2/images/stream.png0000644000175000017500000000170110512232702015705 0ustar mnordstrmnordstrPNG  IHDRĴl;sBIT|dtEXtSoftwarewww.inkscape.org<SIDAT8kW﹓.*mMK*B!l ZcԂ A.k[jbZ&IVmA+S)Mdv̎s|}{;xQVeѲ9;+5Numy}--jxC8OLm|;Ύ?4}1 k6n{fLxm :M[Ů6)n彔GzoƄ?*kV)M4ے U2Mw}7caMn !F(ŸdšPɏ%IJ`f(1}.YOv7mlzm^LfAQ A'lk*sSiYX ۶8fE`i6[>4 20;Bp2 ! @DQ~ِe*##GL̀2HB@ BdY5g[cZ 6o*HWj, $A.=7<}{v E \UUUA%d<|D"$]ֲӆ!ର( =Bki#cMݡ={`y _*Ggԓ,Yr;:^"~?X8'({7Dcc ٢HX @`jirva?8;R*MIENDB`nzb-0.2/images/open.png0000644000175000017500000000403110512232702015352 0ustar mnordstrmnordstrPNG  IHDR szzgAMAOX2tEXtSoftwareAdobe ImageReadyqe<IDATXíW[Pgb/nٛκ;{z:vTpuֶT mQB!@Hs$!grB GTx~0X}g/>ߗ]v uH W*e`[ '3">vH~Y2ϭB9DQ臆={XSR,::NI1>I,`]YSMM4 1y`Ż?ū)LOϮ׼tCO3@{=.4W S2^5C`\&K|H$8As''H׻ "U77N,V>V:q,k,'6?F}oY1~ Z{?0L(CF&e-,!4{Z;~m$~CF=5iu?PZlY]/sq 1% cbjC'ݼ-Ӱ;3&SXO!,CPF BBsZ*tȼE lϛ'Ào%Q(/bM$"R-BySt < ] /yƪԳs LŔbK˫#7G<<b~/=/-:C(QNܿ}3i 3Th58 X\3>GelAC'b ER$nPD΋J~ m['Ѡe")(Ȑx5 >c f]KcNzI1N[;|%jJ0("3 t.| J*qQF@7 jH7'@L{RE qAAͷ]lC(N#KQWHoXQG `4QɫgA@?noiқ*/ru)S)+vQŕky8n3cjs@"mDžPt +4X$ |J8aVU;:!z/]&o8=cn*F׃H:381 ^QO`+] 9g=JUmCeS@%`+njǩD9|6ZZ8/95j'zLIdnFڍv9,9=NrG ;Fc9=pOJnX,P(@!F.[! o<<5iEX?7Ǎ: !p5U!4[7*4nI:rsm$Jesp)>|c.2*\"]~M 6tvw䈶"I m2JxLKIõ.|me6Y\Q{ %&6ǝՂ^NEXx]\UPuTA/ rjQCO-H/5z-Wxܯ\u˯Ev2g`§ҩ;P 9 /"mak^2/f_=IENDB`nzb-0.2/images/nzb.ico0000644000175000017500000001307610512232702015201 0ustar mnordstrmnordstr@@((@555666777888:::;;;<<<???@@@AAAGGGLLLNNNOOOQQQRRRSSSTTTUUUWWWXXXZZZ^^^___```aaabbbcccdddfffggghhhiiijjjlllmmmooopppqqqrrrsssuuuvvvwwwzzz{{{|||}}}~~~~~~{ussssssssssssssssssssssssssssssstx|~yuqmigfcbb`^\\\[XWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWY[]_crsea^\ZXVUSR????+++++!++4?R^Q?.+& -3333444444444444444444#55555 @~r $666OO9NmmmmmjlnjmnnnnnnolijlijilpppppppE_gdecedilij[-~GUlsnpmimmig^Nuug^`^`fficgccqwwrjjecjjjefecjyyLaxuprvurqnnz -~.!o_aahmqoouvqO}}}}}|ii-~%"Oncn]`gmmlqgg||`ee]lhhqqqqzqq}}u-~$C||yDx^[[[a^ll[h%-~Eqynq|yyyajzrrzrwjcjj_runvgofciizf{||||s'-~ IUi}}}}(-~ \mmhmyyymymP}}mm}pupupmqm_[qmm}}j}}(/ PPj}}u}y} 0 Fvmmvvhqvlqmzqvq|rnnni[|rrl 0 Fmmmq > Fdh[hdvvmmmqz G 7mpjzppzyzzzzpvjfz_qq G ,{hndnnwn{{g{lTr{ G ,gaw||U G ,n|rwrh]rrrrxx G )z H (_xinf|{ I (;HH""x { K 1sHH""oo| _ 5u2".99} | g 5g".99yo} g 5f`bT g 6l( pps g 6eu pps g 6X g 7cTS] i 7d".n S n 7iQQS r 8xkQQvpSs 8y[ff66//9Ss 8z{66//{ppSs 9wISs 9wgUSt 9w4x :w[[[[[[[[[[[www*| :w[[[[[[[[[[[[[[BBBBBBBBBBBBBBBw*$~ :wEX*-~ :wvEdyN*XXdEEo3d**3">o"3N>*d>y*-~ *>XEy>"No**N3X*/ dXX*yyoXXXXdyX"oyXyod0 <oXXXodyyddovfj 0 =nwwwwwwwwwwwwww[[[[[[[[[[pwwwwww > 5o[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[BBBBBBBBBBBBBBBB[ H #r J 4sp{{ppjp{{vppv L >>>================2**8EEEEEEEEEEEEEEE8444444DDDDDMO QO))))) nzb-0.2/images/nzb.png0000644000175000017500000001056710512232702015215 0ustar mnordstrmnordstrPNG  IHDR@@iqbKGD pHYs  tIME 96ZޏIDATx[klTշ3y83}`i)b7 &^B׈ >5E_5A11D>" ):Lig93眙s?ؽ=NV+ ܩT l& @<ڛ5kւ_~XE8 p\<\*d,˓m/rAE466ˆ`,IZSnx衇 #q@ajp8D"Z̟?lۇPUd{0Xs*bKfeu0r!Jil=H$l6K⫯²erܬ`K.> ݎnD"477cDpef0fC(dd+Vw֭[H$B`4/AŋlylPD?wGGbv;ߏx$ue]Tai&|{:::6ePH"ӧߥdxߏ=ؔG,Î;nŋSÚf)IhD^JٳgoN~g?l.eI`tiPED{vUUj%N㩧*/ ø#iK h9uVO?TXnuI_Ν;izM $|:ė^zx,իWc:=qDyhoV9sR~ /}fΜ9xWnݵkז $Im0lJ&O>V޽{oYʱcǰ}v]N]_TUUwށllT$!ڈ3%=܃|Ø;w믿^}Ւ#mF7?}h߳xk)[Fȕe}hFHTU[nAd2 xMBQtnC$\|6l@__h:/Fx<^Ywww޽{7 -nOӿ{p: RUUUUeYT6l؀v[fΜIߩnv;^ӧOz -¾}ba*9Bc)Ö-[tf~)|><4fEQC#CFaW[ZZq:I}aڵ4[`$bٲe7e'O+WX磬=Av@z t#sΝx'u.Zo&,~ dɓ ߱cEQD:(8#Ը[v`(B$$2HqfY!~fL(8,&Rd)Ο?Af>qp88 8l۶ ӦM[l/8ߵk$IB*qصk~j4rx+W( 48wQZyb(%d2ѵH\>B$|>{bH555x"0"fat3$P(x{nnxc :|3.|PU]]]b`YL'IzRDUUʵUWWTd07ouc+kIʉDMM 2 ^/X(d2H$32&BCk:l6OeYyL$ 5NzǠx

"2 e\4E(4l6EQ0o<_A[o$؆B!Z6;gE:JACCv8$v;X=#j٘p8Lnhh(IŰ,+Wнxh^_`tɱX,3gΘWb-uk '"$8(rZP,B Ve9sFeyD<Ϗ9KLR(d2TyP( h( Ο?O-(  n7L&X(c v d2Q:-qqq0 0LPktdsd /aFӕЌUUdZ]ex!4`dYveYtwwj"h4rAQ}>F|VT VRǃzz "Fny$Ib18vp7&hb\PXLlQMB, ϣP(h4"bΜ9 jbѵh@R,:%y,Kp5D"U(h6p8hnnC .``YbQNR,0 0 $ v(aqdYl6 ѠH*=J0qyKrJWh`0hrx<Np(/Db[,|f3XBOZ>xK)։(eYҥ)!Y( d(Pd;Gwމ-dre,Cee%, vP@.AyvTU `@X`7$Qr_ea ,˔k {zz  ,BUU M hed2}/ZZ2b[XB.EQ`4uHBz$0:|*Fv7E P(ajF#F`o, bNg]20VnF[Z֒> D.~H$(gS'bQy[pq>LcczKYcIdY~A.]t@e,*p##0|k?d_9(i}毒⨏NH0L²0r|G)Ե?}td w@@Y0%g["Go"_8dXNIENDB`nzb-0.2/images/start.png0000644000175000017500000000217610512232702015556 0ustar mnordstrmnordstrPNG  IHDRĴl;bKGD pHYs B(xtIME 4Her IDAT8˭]lU39meeY>"ڒVX0B4F|5HUR FilD4ȋh 16ƈ4AT%6ݶvggw|hwlPL'7s=3W`{?ƦH\om~8ያ#>1UeNϸT:CdVۻ_;rĖ"U[pm##J+PvutPv=ܹϥ:p+bkw'tO>P+Ft6N<@}A/.yYb5%3}}pG_6G$TUHNeRELhekԡ>;r?Ϫ I۩7/Jrk|ow_&ǎdۺe%m޹н}lHKW>q`p3>;>Ap)K۸yhە<})0K`Dɦpf z]qYA\-nOwsǾ|>ZP҂}4,NKp~ȯ&F>A"sU`/]w=pwg,hBD:L (V'R!ScXRDL}}DT\ĭJq \{~_Cn4Dk;1&m ^;PPW`c ,|ŋ`#!fo|'*T_M=(K,,lՄN>ٹ}a`;.YnFR ̐\-plKZF'[(S|BcY)%h?<<ϫ.=(y^D@k̼ B!YKXm%V|rR?K,сhIJ툀Pf0 }?p0!]_! 4v9 I#zXVfi*N2l'^UL0uBVXC, ;v:u  dw/iZU{dpIu\WD S3J-k'պ1"ںXIؒ%"5D4ϊ_I]ZpfxThކL#%dt1؈z  nH5#b`iXuV,NC:"PzةculLǖhApAq]!q1cHd2*RJphNvM_ u臝Wnչy<^v PӤ\RT<.q,y-0F@0F&[hp=L#'~0u3w8b %%eV$V@(5EHCP%k37|s%8o8uxVJQCX\7N:bYkvB==0 "DXJ-+[^V27F .dh3%,XgO|_ÍM-D h[L{۾+mjnhhI']P(WMOL}߮޾M:!v#Ș&v6[' ]x8#JxwF ; 77{]]dB!rJ,}H|L ieejme޽BV # [lh'hB OZ3gBRڊmF8iB.źT*y!@)*lӧYq /?ihJ1p0r9R()IU*Ò*`r pz<8^"/bmc?Gxm=%tvM4FnaIF&i4 JJX|3HI0<^:v gY:EhBPs(Sĥ$C=~0i?*)HIr^ﯯwIx*+YY[_mxԭe2h08s ttiWU/dbmxX1|V}ֆ %ee/hy%!R`9< v oB B䣠os8SUyP,y@xP(0 (?dyIENDB`nzb-0.2/images/multimedia2.png0000644000175000017500000000450111731364766016653 0ustar mnordstrmnordstrPNG  IHDR szz IDATXy?wmoƙqDjEqǥR8*.FMԶFkKj1jŽKZEJ0nC+VE-X ~ox34{w{sֲC3%Sj^tLl #cY `>PM,1gE̙>sޓ;u{>A$ {G_[où}|"N?gyAa +@) 0lٲ{a`C/>&;I7yY&&)y|>ZbĦoN(9 O=-XfrK!>vk-Zko.OFn~\/}WOlKjUrE[K )YVlu-(AgT˜1ASX|FE;Y-SfgN96ͰX@9fOaެ)T"z!Oqן_"n{<|qel>(#G*e*0 )W*HaYv ,up˯;VKHZORaxp̞2. {:$ͩU>zR|x͙{^LI8T6r8ϔ)v tw=*UU0s^y<*,GPW䍷׳o%aa5rt8OXdbx!R*H)$M1f4+,琦9EaZ1SPCVZ"{4Չ%qڒtfgVX@F8Z𙴵p= 싯#hj ,ϙ2撋oKBJzS%2 ńjLQe]Ո!徇_Ešߧ#+`,MQ?Vը!VYNV=(-"GR6~ SE s c(..]󿱀3z&M3-G[K3iQ((k N%-Co'"<1(%y⩗Yr#$I z&NR~uC\%T*/znzXp]3@(EpԨ?Jȏv*cRJd!M ^N9H\k͟MQ bN4BDIs􎴺IXZZ9L58CxR #:'ʱG"4zƸC/ß`sqW)ю@n~G>6qR P"o+й( sfMc&VdyAwYiA@#D) E86rƛ" `T&6(~J @k|ϭSi{Ƣ"zCZo喰Ƅ긿QK6'fچWcVb8B+˛G#I2g*PT8޿F9Hݮ[Ż0[eM9c8lX\hbmi ?:Mehy47>JmMS)=h<#s,# jYP+Ցlciqm폏a6:"%dq9A;J(QJy>ADPj"JAHq]A{-dyd7eۂQ:¯QJn滖}