FU Logo
  • Startseite
  • Kontakt
  • Impressum
  • Home
  • Listenauswahl
  • Anleitungen

Re: [linux-minidisc] implementing netmd support in the gui application

<-- thread -->
<-- date -->
  • From: Thomas Arp <manner.moe@gmx.de>
  • To: linux-minidisc@lists.fu-berlin.de
  • Date: Thu, 10 Jan 2013 00:18:51 +0100
  • Subject: Re: [linux-minidisc] implementing netmd support in the gui application

Hi,
as some patches have been committed since i started implementation of netmd support i rebased my local master branch now and updated my local work branch. You can find my work at netmd_integration_latest branch on my github account [1]. Also i made a patch which can be applied to the current master repository attached to this email.

Thomas

[1] http://github.com/tharp/linux-minidisc
>From 6444acb4e5fdc7c4b77d37cf26306a639722fe96 Mon Sep 17 00:00:00 2001
From: Thomas Arp <manner.moe@gmx.de>
Date: Wed, 9 Jan 2013 23:57:41 +0100
Subject: [PATCH] implement netmd support in qhimdtransfer, also some basic
 rearrangement needed for switching between himd and netmd
 devices, move himd transfer routines to new class
 QHiMDDevice, some minor bugs fixed

---
 qhimdtransfer/qhimddetection.cpp    | 262 +++++++++++++++++---
 qhimdtransfer/qhimddetection.h      | 106 +++++---
 qhimdtransfer/qhimdmainwindow.cpp   | 444 ++++++++-------------------------
 qhimdtransfer/qhimdmainwindow.h     |  30 +--
 qhimdtransfer/qhimdmainwindow.ui    |  18 +-
 qhimdtransfer/qhimdmodel.cpp        | 289 ----------------------
 qhimdtransfer/qhimdmodel.h          |  68 -----
 qhimdtransfer/qhimdtransfer.pro     |  13 +-
 qhimdtransfer/qhimduploaddialog.cpp |   4 +-
 qhimdtransfer/qhimduploaddialog.h   |   4 +-
 qhimdtransfer/qhimdwindetection.cpp | 203 ++++++++++-----
 qhimdtransfer/qmddevice.cpp         | 480 ++++++++++++++++++++++++++++++++++++
 qhimdtransfer/qmddevice.h           |  98 ++++++++
 qhimdtransfer/qmdmodel.cpp          | 358 +++++++++++++++++++++++++++
 qhimdtransfer/qmdmodel.h            |  80 ++++++
 qhimdtransfer/qmdtrack.cpp          | 204 +++++++++++++++
 qhimdtransfer/qmdtrack.h            |  81 ++++++
 17 files changed, 1888 insertions(+), 854 deletions(-)
 delete mode 100644 qhimdtransfer/qhimdmodel.cpp
 delete mode 100644 qhimdtransfer/qhimdmodel.h
 create mode 100644 qhimdtransfer/qmddevice.cpp
 create mode 100644 qhimdtransfer/qmddevice.h
 create mode 100644 qhimdtransfer/qmdmodel.cpp
 create mode 100644 qhimdtransfer/qmdmodel.h
 create mode 100644 qhimdtransfer/qmdtrack.cpp
 create mode 100644 qhimdtransfer/qmdtrack.h

diff --git a/qhimdtransfer/qhimddetection.cpp b/qhimdtransfer/qhimddetection.cpp
index d563587..d0839bb 100644
--- a/qhimdtransfer/qhimddetection.cpp
+++ b/qhimdtransfer/qhimddetection.cpp
@@ -1,73 +1,279 @@
 #include <QtCore/QDebug>
 #include "qhimddetection.h"
+#include <QDebug>
+
+void QHiMDDetection::clearDeviceList()
+{
+    QMDDevice * mddev;
+    QNetMDDevice * nmddev;
+    int i = 0;
+
+    while( i < dlist.count() )
+    {
+        mddev = dlist.at(i);
+        if(mddev->deviceType() == NETMD_DEVICE)
+        {
+            nmddev = static_cast<QNetMDDevice *>(mddev);
+            if(nmddev->isOpen())
+                nmddev->close();
+            delete nmddev;
+            nmddev = NULL;
+            dlist.removeAt(i);
+            continue;
+        }
+        else if(mddev->deviceType() == HIMD_DEVICE)
+        {
+            remove_himddevice(mddev->path());  // uses platform dependent function if available
+            continue;
+        }
+    }
+
+    if(!dlist.isEmpty())
+        dlist.clear();
+    emit deviceListChanged(dlist);
+}
 
 QHiMDDetection::QHiMDDetection(QObject *parent) :
     QObject(parent)
 {
 }
 
-himd_device *QHiMDDetection::find_by_path(QString path)
+QHiMDDetection::~QHiMDDetection()
 {
-    for (int i = 0; i < device_list.size(); i++)
-        if(device_list.at(i)->path == path)
-            return device_list.at(i);
+    clearDeviceList();
+    cleanup_netmd_list();
+}
 
-    return NULL;
+void QHiMDDetection::cleanup_netmd_list()
+{
+    if(dev_list != NULL)
+        netmd_clean(&dev_list);
+}
+
+void QHiMDDetection::rescan_netmd_devices()
+{
+    QNetMDDevice * dev;
+    int i = 0;
+
+    // find and remove netmd devices
+    while(i < dlist.count())
+    {
+        if(dlist.at(i)->deviceType() != NETMD_DEVICE)
+        {
+            i++;
+            continue;
+        }
+        dev = static_cast<QNetMDDevice *>(dlist.at(i));
+        if(dev->isOpen())
+            dev->close();
+
+        delete dev;
+        dev = NULL;
+        dlist.removeAt(i);
+    }
+
+    netmd_clean(&dev_list);
+    dev_list = NULL;
+
+    emit deviceListChanged(dlist);
+    scan_for_netmd_devices();
 }
 
-// slots
+void QHiMDDetection::scan_for_minidisc_devices()
+{
+    os_data data;
 
-void QHiMDDetection::himd_busy(QString path)
+    /* create device entry for disc images first */
+    QHiMDDevice * mddev = new QHiMDDevice();
+    data.md_inserted = true;
+    mddev->setName("disc image");
+    mddev->setPlatformData(data);
+    dlist.append(mddev);
+    emit deviceListChanged(dlist);
+
+    scan_for_himd_devices();
+    scan_for_netmd_devices();
+}
+
+void QHiMDDetection::remove_himddevice(QString path)
 {
-    himd_device * dev = find_by_path(path);
-    if (!dev)
+    QHiMDDevice * dev = static_cast<QHiMDDevice *>(find_by_path(path));
+    int i = dlist.indexOf(find_by_path(path));
+
+    if(i < 0)
         return;
 
-    dev->is_busy = true;
-    qDebug() << "himd device at " + dev->path + " : device busy, starting transfer";
+    if(dev->isOpen())
+        dev->close();
+    delete dev;
+    dev = NULL;
+
+    dlist.removeAt(i);
 }
 
-void QHiMDDetection::himd_idle(QString path)
+void QHiMDDetection::scan_for_netmd_devices()
 {
-    himd_device * dev = find_by_path(path);
-    if (!dev)
+    netmd_device * md;
+    netmd_error error = netmd_init(&dev_list);
+    struct libusb_device_descriptor desc;
+    QNetMDDevice * mddev;
+
+    if (error != NETMD_NO_ERROR)
         return;
 
-    dev->is_busy = false;
-    qDebug() << "himd device at " + dev->path + " : device idle, transfer complete";
+    md = dev_list;  // pick first device
+
+    while( md != NULL) {
+        libusb_get_device_descriptor(md->usb_dev, &desc);
+        mddev = new QNetMDDevice();
+        mddev->setName(identify_usb_device(desc.idVendor, desc.idProduct));
+        mddev->setUsbDevice(md);
+        dlist.append(mddev);
+        emit deviceListChanged(dlist);
+        md = md->link;  // pick next device
+    }
+}
+
+QMDDevice *QHiMDDetection::find_by_path(QString path)
+{
+    QMDDevice * mddev;
+
+    foreach(mddev, dlist)
+    {
+        if(mddev->path() == path)
+            return mddev;
+    }
+    return NULL;
+}
+
+QMDDevice *QHiMDDetection::find_by_name(QString name)
+{
+    QMDDevice * mddev;
+
+    foreach(mddev, dlist)
+    {
+        if(mddev->name() == name)
+            return mddev;
+    }
+    return NULL;
 }
 
 const char * identify_usb_device(int vid, int pid)
 {
+    if(vid == SHARP)
+    {
+        switch(pid)
+            {
+            case IM_MT880H:
+                return "SHARP IM-MT880H / IM-MT899H (NetMD)";
+            case IM_DR400:
+                return "SHARP IM-DR400 / IM-DR410 (NetMD)";
+            case IM_DR80:
+                return "SHARP IM-DR80 / IM-DR420/ IM-DR580 or KENWOOD DMC-S9NET (NetMD)";
+            }
+    }
+
     if (vid != SONY)
         return NULL;
 
     switch (pid)
     {
-        case MZ_NH1:
+        case MZ_NH1_HIMD:
             return "SONY MZ-NH1";
-        case MZ_NH3D:
+        case MZ_NH3D_HIMD:
             return "SONY MZ-NH3D";
-        case MZ_NH900:
+        case MZ_NH900_HIMD:
             return "SONY MZ-NH900";
-        case MZ_NH700:
+        case MZ_NH700_HIMD:
             return "SONY MZ-NH700 / MZ-NH800";
-        case MZ_NH600:
+        case MZ_NH600_HIMD:
             return "SONY MZ-NH600(D)";
-        case LAM_3:
+        case LAM_3_HIMD:
             return "SONY LAM-3";
-        case MZ_DH10P:
+        case MZ_DH10P_HIMD:
             return "SONY MZ-DH10P";
-        case MZ_RH10:
+        case MZ_RH10_HIMD:
             return "SONY MZ-RH10";
-        case MZ_RH910:
+        case MZ_RH910_HIMD:
             return "SONY MZ-RH910";
-        case CMT_AH10:
+        case CMT_AH10_HIMD:
             return "SONY CMT-AH10";
-        case DS_HMD1:
+        case DS_HMD1_HIMD:
             return "SONY DS-HMD1";
-        case MZ_RH1:
+        case MZ_RH1_HIMD:
             return "SONY MZ-RH1";
+        case PCLK_XX:
+            return "SONY PCLK-XX (NetMD)";
+        case UNKNOWN_A:
+            return "SONY (unknown model, NetMD)";
+        case MZ_N1:
+            return "SONY MZ-N1 (NetMD)";
+        case UNKNOWN_B:
+            return "SONY (unknown model, NetMD)";
+        case LAM_1:
+            return "Sony LAM-1 (NetMD)";
+        case MDS_JE780:
+            return "SONY MDS-JE780 / MDS-JE980 (NetMD)";
+        case MZ_N505:
+            return "SONY MZ-N505 (NetMD)";
+        case MZ_S1:
+            return "SONY MZ-S1 (NetMD)";
+        case MZ_N707:
+            return "SONY MZ-N707 (NetMD)";
+        case CMT_C7NT:
+            return "SONY CMT-C7NT (NetMD)";
+        case PCGA_MDN1:
+            return "SONY PCGA-MDN1 (NetMD)";
+        case CMT_L7HD:
+            return "SONY CMT-L7HD (NetMD)";
+        case MZ_N10:
+            return "SONY MZ-N10 (NetMD)";
+        case MZ_N910:
+            return "SONY MZ-N910 (NetMD)";
+        case MZ_N710:
+            return "SONY MZ-N710 / MZ-NE810 / MZ-NF810 (NetMD)";
+        case MZ_N510:
+            return "SONY MZ-N510 (NetMD)";
+        case MZ_NE410:
+            return "SONY MZ-NE410 / MZ-DN430 / MZ-NF520 (NetMD)";
+        case MZ_NE810:
+            return "SONY MZ-NE810 / MZ-NE910 (NetMD)";
+        case CMT_M333NT:
+            return "SONY CMT-M333NT / CMT_M373NT (NetMD)";
+        case LAM_10:
+            return "SONY LAM-10 (NetMD)";
+        case AIWA_AM_NX1:
+            return "AIWA AM-NX1 (NetMD)";
+        case AIWA_AM_NX9:
+            return "AIWA AM-NX9 (NetMD)";
+        case MZ_NH1:
+            return "SONY MZ-NH1 (NetMD)";
+        case MZ_NH3D:
+            return "SONY MZ-NH3D (NetMD)";
+        case MZ_NH900:
+            return "SONY MZ-NH900 (NetMD)";
+        case MZ_NH700:
+            return "SONY MZ-NH700 / MZ-NH800 (NetMD)";
+        case MZ_NH600:
+            return "SONY MZ-NH600 / MZ-NH600D (NetMD)";
+        case MZ_N920:
+            return "SONY MZ-N920 (NetMD)";
+        case LAM_3:
+            return "SONY LAM-3 (NetMD)";
+        case MZ_DH10P:
+            return "SONY MZ-DH10P (NetMD)";
+        case MZ_RH10:
+            return "SONY MZ-RH10 (NetMD)";
+        case MZ_RH910:
+            return "SONY MZ-RH910 (NetMD)";
+        case CMT_AH10_A:
+            return "SONY CMT-AH10 (NetMD)";
+        case CMT_AH10_B:
+            return "SONY CMT-AH10 (NetMD)";
+        case DS_HMD1:
+            return "SONY DS-HMD1 (NetMD)";
+        case MZ_RH1:
+            return "SONY MZ-RH1 (NetMD)";
     }
     return NULL;
 }
diff --git a/qhimdtransfer/qhimddetection.h b/qhimdtransfer/qhimddetection.h
index 5c30be9..9d53305 100644
--- a/qhimdtransfer/qhimddetection.h
+++ b/qhimdtransfer/qhimddetection.h
@@ -4,52 +4,98 @@
 #include <QObject>
 #include <QList>
 #include <QString>
+#include <qmddevice.h>
 
-#define SONY 0x054c         //known himd-mode product IDs
-#define MZ_NH1 0x017f
-#define MZ_NH3D 0x0181
-#define MZ_NH900 0x0183
-#define MZ_NH700 0x0185
-#define MZ_NH600 0x0187
-#define LAM_3 0x018a
-#define MZ_DH10P 0x01ea
-#define MZ_RH10 0x021a
-#define MZ_RH910 0x021c
-#define CMT_AH10 0x022d
-#define DS_HMD1 0x023d
-#define MZ_RH1 0x0287
-
-struct himd_device {
-                    bool is_busy;
-                    QString path;
-                    bool md_inserted;
-                    QString recorder_name;
-                    virtual ~himd_device(){} /* for polymorphic delete */
-                    };
+// known vendor IDs
+#define SONY 0x054c
+#define SHARP 0x4dd
+
+// known himd-mode product IDs
+#define MZ_NH1_HIMD 0x017f
+#define MZ_NH3D_HIMD 0x0181
+#define MZ_NH900_HIMD 0x0183
+#define MZ_NH700_HIMD 0x0185
+#define MZ_NH600_HIMD 0x0187
+#define LAM_3_HIMD 0x018b
+#define MZ_DH10P_HIMD 0x01ea
+#define MZ_RH10_HIMD 0x021a
+#define MZ_RH910_HIMD 0x021c
+#define CMT_AH10_HIMD 0x022d
+#define DS_HMD1_HIMD 0x023d
+#define MZ_RH1_HIMD 0x0287
+
+// known Sony/Aiwa netmd-mode product IDs
+#define PCLK_XX 0x34
+#define UNKNOWN_A 0x36
+#define MZ_N1 0x75
+#define UNKNOWN_B 0x7c
+#define LAM_1 0x80
+#define MDS_JE780 0x81            // or MDS-JE980
+#define MZ_N505 0x84
+#define MZ_S1 0x85
+#define MZ_N707 0x86
+#define CMT_C7NT 0x8e
+#define PCGA_MDN1 0x97
+#define CMT_L7HD 0xad
+#define MZ_N10 0xc6
+#define MZ_N910 0xc7
+#define MZ_N710 0xc8               // or MZ-NE810/NF810
+#define MZ_N510 0xc9               // or MZ-NF610
+#define MZ_NE410 0xca              // or MZ-DN430/NF520
+#define MZ_NE810 0xeb              // or MZ-NE910
+#define CMT_M333NT 0xe7            // or CMT-M373NT
+#define LAM_10 0x101
+#define AIWA_AM_NX1 0x113
+#define AIWA_AM_NX9 0x14c
+#define MZ_NH1 0x17e
+#define MZ_NH3D 0x180
+#define MZ_NH900 0x182
+#define MZ_NH700 0x184             // or MZ-NH800
+#define MZ_NH600 0x186             // or MZ-NH600D
+#define MZ_N920 0x188
+#define LAM_3 0x18a
+#define MZ_DH10P 0x1e9
+#define MZ_RH10 0x219
+#define MZ_RH910 0x21b
+#define CMT_AH10_A 0x21d
+#define CMT_AH10_B 0x22c
+#define DS_HMD1 0x23c
+#define MZ_RH1 0x286
+
+// known Sharp netmd-mode product IDs
+#define IM_MT880H 0x7202            // or IM-MT899H
+#define IM_DR400 0x9013             // or IM-DR410
+#define IM_DR80 0x9014              // or IM-DR420/DR580 / Kenwood DMC-S9NET
 
 const char * identify_usb_device(int vid, int pid);
 
+typedef QList<QMDDevice *> QMDDevicePtrList;
+
 class QHiMDDetection : public QObject {
     Q_OBJECT
     Q_DISABLE_COPY(QHiMDDetection)
 
 protected:
-    QList<himd_device *> device_list;
+    QMDDevicePtrList dlist;
+    netmd_device * dev_list;
 public:
     explicit QHiMDDetection(QObject *parent = 0);
-    virtual ~QHiMDDetection() {}
+    virtual ~QHiMDDetection();
+    virtual void clearDeviceList();
+    virtual void cleanup_netmd_list();
+    void rescan_netmd_devices();
+    void scan_for_minidisc_devices();
     virtual void scan_for_himd_devices(){}
-    himd_device *find_by_path(QString path);
-
-protected slots:
-    virtual void himd_busy(QString path);
-    virtual void himd_idle(QString path);
+    virtual void remove_himddevice(QString path);
+    void scan_for_netmd_devices();
+    QMDDevice *find_by_path(QString path);
+    QMDDevice *find_by_name(QString name);
 
 signals:
-    void himd_found(QString path);
-    void himd_removed(QString path);
+    void deviceListChanged(QMDDevicePtrList list);
 };
 
 QHiMDDetection * createDetection(QObject * parent = NULL);
 
 #endif // QHIMDDETECTION_H
+
diff --git a/qhimdtransfer/qhimdmainwindow.cpp b/qhimdtransfer/qhimdmainwindow.cpp
index e2675d9..a406ea5 100644
--- a/qhimdtransfer/qhimdmainwindow.cpp
+++ b/qhimdtransfer/qhimdmainwindow.cpp
@@ -1,213 +1,11 @@
 #include "qhimdmainwindow.h"
 #include "ui_qhimdmainwindow.h"
 #include "qhimdaboutdialog.h"
-#include "qhimduploaddialog.h"
 #include <QtGui/QMessageBox>
-#include <QtGui/QApplication>
 
 #include <QtCore/QDebug>
 
 
-QString QHiMDMainWindow::dumpmp3(const QHiMDTrack & trk, QString file)
-{
-    QString errmsg;
-    struct himd_mp3stream str;
-    struct himderrinfo status;
-    unsigned int len;
-    const unsigned char * data;
-    QFile f(file);
-
-    if(!f.open(QIODevice::ReadWrite))
-    {
-        return tr("Error opening file for MP3 output");
-    }
-    if(!(errmsg = trk.openMpegStream(&str)).isNull())
-    {
-        f.remove();
-        return tr("Error opening track: ") + errmsg;
-    }
-    while(himd_mp3stream_read_block(&str, &data, &len, NULL, &status) >= 0)
-    {
-        if(f.write((const char*)data,len) == -1)
-        {
-            errmsg = tr("Error writing audio data");
-            goto clean;
-        }
-        uploadDialog->blockTransferred();
-        QApplication::processEvents();
-        if(uploadDialog->upload_canceled())
-        {
-            errmsg = tr("upload aborted by the user");
-            goto clean;
-        }
-
-    }
-    if(status.status != HIMD_STATUS_AUDIO_EOF)
-        errmsg = tr("Error reading audio data: ") + status.statusmsg;
-
-clean:
-    f.close();
-    himd_mp3stream_close(&str);
-    if(!errmsg.isNull())
-        f.remove();
-    return errmsg;
-}
-
-static inline TagLib::String QStringToTagString(const QString & s)
-{
-    return TagLib::String(s.toUtf8().data(), TagLib::String::UTF8);
-}
-
-static void addid3tag(QString title, QString artist, QString album, QString file)
-{
-#ifdef Q_OS_WIN
-    TagLib::FileRef f(file.toStdWString().c_str());
-#else
-    TagLib::FileRef f(file.toUtf8().data());
-#endif
-    TagLib::Tag *t = f.tag();
-    t->setTitle(QStringToTagString(title));
-    t->setArtist(QStringToTagString(artist));
-    t->setAlbum(QStringToTagString(album));
-    t->setComment("*** imported from HiMD via QHiMDTransfer ***");
-    f.file()->save();
-}
-
-QString QHiMDMainWindow::dumpoma(const QHiMDTrack & track, QString file)
-{
-    QString errmsg;
-    struct himd_nonmp3stream str;
-    struct himderrinfo status;
-    unsigned int len;
-    const unsigned char * data;
-    QFile f(file);
-
-    if(!f.open(QIODevice::ReadWrite))
-        return tr("Error opening file for ATRAC output");
-
-    if(!(errmsg = track.openNonMpegStream(&str)).isNull())
-    {
-        f.remove();
-        return tr("Error opening track: ") + status.statusmsg;
-    }
-
-    if(f.write(track.makeEA3Header()) == -1)
-    {
-        errmsg = tr("Error writing header");
-        goto clean;
-    }
-    while(himd_nonmp3stream_read_block(&str, &data, &len, NULL, &status) >= 0)
-    {
-        if(f.write((const char*)data,len) == -1)
-        {
-            errmsg = tr("Error writing audio data");
-            goto clean;
-        }
-        uploadDialog->blockTransferred();
-        QApplication::processEvents();
-        if(uploadDialog->upload_canceled())
-        {
-            errmsg = QString("upload aborted by the user");
-            goto clean;
-        }
-    }
-    if(status.status != HIMD_STATUS_AUDIO_EOF)
-        errmsg = QString("Error reading audio data: ") + status.statusmsg;
-
-clean:
-    f.close();
-    himd_nonmp3stream_close(&str);
-
-    if(!errmsg.isNull())
-        f.remove();
-    return errmsg;
-}
-
-QString QHiMDMainWindow::dumppcm(const QHiMDTrack & track, QString file)
-{
-    struct himd_nonmp3stream str;
-    struct himderrinfo status;
-    unsigned int len, i;
-    int left, right;
-    int clipcount;
-    QString errmsg;
-    QFile f(file);
-    const unsigned char * data;
-    sox_format_t * out;
-    sox_sample_t soxbuf [HIMD_MAX_PCMFRAME_SAMPLES * 2];
-    sox_signalinfo_t signal_out;
-
-    signal_out.channels = 2;
-    signal_out.length = 0;
-    signal_out.precision = 16;
-    signal_out.rate = 44100;
-
-    if(!(out = sox_open_write(file.toUtf8(), &signal_out, NULL, NULL, NULL, NULL)))
-        return tr("Error opening file for WAV output");
-
-    if(!(errmsg = track.openNonMpegStream(&str)).isNull())
-    {
-        f.remove();
-        return tr("Error opening track: ") + status.statusmsg;
-    }
-
-    while(himd_nonmp3stream_read_block(&str, &data, &len, NULL, &status) >= 0)
-    {
-
-      for(i = 0; i < len/4; i++) {
-
-        left = data[i*4]*256+data[i*4+1];
-        right = data[i*4+2]*256+data[i*4+3];
-        if (left > 0x8000) left -= 0x10000;
-        if (right > 0x8000) right -= 0x10000;
-
-        soxbuf[i*2] = SOX_SIGNED_16BIT_TO_SAMPLE(left, clipcount);
-        soxbuf[i*2+1] = SOX_SIGNED_16BIT_TO_SAMPLE(right, clipcount);
-        (void)clipcount; /* suppess "is unused" warning */
-      }
-
-      if (sox_write(out, soxbuf, len/2) != len/2)
-      {
-            errmsg = tr("Error writing audio data");
-            goto clean;
-      }
-      uploadDialog->blockTransferred();
-      QApplication::processEvents();
-      if(uploadDialog->upload_canceled())
-      {
-            errmsg = QString("upload aborted by the user");
-            goto clean;
-      }
-    }
-    if(status.status != HIMD_STATUS_AUDIO_EOF)
-        errmsg = QString("Error reading audio data: ") + status.statusmsg;
-
-clean:
-    sox_close(out);
-    himd_nonmp3stream_close(&str);
-
-    if(!errmsg.isNull())
-        f.remove();
-    return errmsg;
-}
-
-void QHiMDMainWindow::checkfile(QString UploadDirectory, QString &filename, QString extension)
-{
-    QFile f;
-    QString newname;
-    int i = 2;
-
-    f.setFileName(UploadDirectory + "/" + filename + extension);
-    while(f.exists())
-    {
-        newname = filename + " (" + QString::number(i) + ")";
-        f.setFileName(UploadDirectory + "/" + newname + extension);
-        i++;
-    }
-    if(!newname.isEmpty())
-        filename = newname;
-}
-
 void QHiMDMainWindow::set_buttons_enable(bool connect, bool download, bool upload, bool rename, bool del, bool format, bool quit)
 {
     ui->action_Connect->setEnabled(connect);
@@ -221,13 +19,10 @@ void QHiMDMainWindow::set_buttons_enable(bool connect, bool download, bool uploa
     ui->download_button->setEnabled(download);
 }
 
-void QHiMDMainWindow::init_himd_browser()
+void QHiMDMainWindow::init_himd_browser(QAbstractListModel * model)
 {
-    int i = 0;
+    ui->TrackList->setModel(model);
 
-    ui->TrackList->setModel(&trackmodel);
-    for(;i < trackmodel.columnCount(); i++)
-        ui->TrackList->resizeColumnToContents(i);
     QObject::connect(ui->TrackList->selectionModel(), SIGNAL(selectionChanged (const QItemSelection &, const QItemSelection &)),
                      this, SLOT(handle_himd_selection_change(const QItemSelection&, const QItemSelection&)));
 }
@@ -237,7 +32,6 @@ void QHiMDMainWindow::init_local_browser()
     QStringList DownloadFileList;
     localmodel.setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
     localmodel.setNameFilters(QStringList() << "*.mp3" << "*.wav" << "*.oma");
-    localmodel.setSelectableExtensions(trackmodel.downloadableFileExtensions());
     localmodel.setNameFilterDisables(false);
     localmodel.setReadOnly(false);
     localmodel.setRootPath("/");
@@ -259,8 +53,9 @@ void QHiMDMainWindow::save_window_settings()
 
     settings.setValue("geometry", QMainWindow::saveGeometry());
     settings.setValue("windowState", QMainWindow::saveState());
-    for(;i < trackmodel.columnCount(); i++)
+    for(;i < htmodel.columnCount(); i++)
         settings.setValue("himd_browser" + QString::number(i), ui->TrackList->columnWidth(i));
+    /* TODO: save and restore columnWidth for netmd model */
 }
 
 void QHiMDMainWindow::read_window_settings()
@@ -270,7 +65,7 @@ void QHiMDMainWindow::read_window_settings()
 
     QMainWindow::restoreGeometry(settings.value("geometry").toByteArray());
     QMainWindow::restoreState(settings.value("windowState").toByteArray());
-    for(; i < trackmodel.columnCount(); i++)
+    for(; i < htmodel.columnCount(); i++)
     {
         width = settings.value("himd_browser" + QString::number(i), 0).toInt();
         if(width != 0)
@@ -280,105 +75,73 @@ void QHiMDMainWindow::read_window_settings()
 
 bool QHiMDMainWindow::autodetect_init()
 {
-    int k;
-
-    k = QObject::connect(detect, SIGNAL(himd_found(QString)), this, SLOT(himd_found(QString)));
-    k += QObject::connect(detect, SIGNAL(himd_removed(QString)), this, SLOT(himd_removed(QString)));
-
-    if(!k)
+    if(!QObject::connect(detect, SIGNAL(deviceListChanged(QMDDevicePtrList)), this, SLOT(device_list_changed(QMDDevicePtrList))))
         return false;
 
-    QObject::connect(this, SIGNAL(himd_busy(QString)), detect, SLOT(himd_busy(QString)));
-    QObject::connect(this, SIGNAL(himd_idle(QString)), detect, SLOT(himd_idle(QString)));
-
-    detect->scan_for_himd_devices();
+    detect->scan_for_minidisc_devices();
     return true;
 }
 
-void QHiMDMainWindow::open_himd_at(const QString & path)
+void QHiMDMainWindow::setCurrentDevice(QMDDevice *dev)
 {
-    QMessageBox himdStatus;
-    QString error;
+    current_device = dev;
+    QObject::connect(current_device, SIGNAL(closed()), this, SLOT(current_device_closed()));
+}
 
-    error = trackmodel.open(path.toAscii());
+void QHiMDMainWindow::open_device(QMDDevice * dev)
+{
+    QMessageBox mdStatus;
+    QString error;
 
-    if (!error.isNull()) {
-        himdStatus.setText(tr("Error opening HiMD data. Make sure you chose the proper root directory of your HiMD-Walkman.\n") + error);
-        himdStatus.exec();
+    if(dev->deviceType() == HIMD_DEVICE && dev->path().isEmpty())
+    {
+        mdStatus.setText(tr("Error opening himd device/disc image , no device path given\nPlease use connect button to set the path to the himd device/disc image"));
+        mdStatus.exec();
         set_buttons_enable(1,0,0,0,0,0,1);
         return;
     }
 
-    ui->himdpath->setText(path);
-    settings.setValue("lastHiMDDirectory", path);
+    if(current_device)
+        current_device_closed();
 
-    himd_device * dev = detect->find_by_path(path);
-    if(dev)
-        ui->statusBar->showMessage(dev->recorder_name);
+    setCurrentDevice(dev);
+    if(current_device->deviceType() == NETMD_DEVICE) {
+        error = ntmodel.open(current_device);
+        init_himd_browser(&ntmodel);
+        localmodel.setSelectableExtensions(ntmodel.downloadableFileExtensions());
+    }
+    else if(current_device->deviceType() == HIMD_DEVICE) {
+        error = htmodel.open(current_device);
+        init_himd_browser(&htmodel);
+        localmodel.setSelectableExtensions(htmodel.downloadableFileExtensions());
+    }
     else
-        ui->statusBar->clearMessage();
+    {
+        mdStatus.setText(tr("Error opening minidisc device, unknown/invalid device type given\n"));
+        mdStatus.exec();
+        set_buttons_enable(1,0,0,0,0,0,1);
+        return;
+    }
 
+    if (!error.isNull()) {
+        mdStatus.setText(tr("Error opening minidisc device:\n") + error);
+        mdStatus.exec();
+        set_buttons_enable(1,0,0,0,0,0,1);
+        return;
+    }
+
+    ui->DiscTitle->setText(current_device->discTitle());
     set_buttons_enable(1,0,0,1,1,1,1);
 }
 
 void QHiMDMainWindow::upload_to(const QString & UploadDirectory)
 {
-    emit himd_busy(ui->himdpath->text());
+    QMDTrackIndexList tlist;
 
-    QHiMDTrackList tracks = trackmodel.tracks(ui->TrackList->selectionModel()->selectedRows(0));
+    foreach(QModelIndex index, ui->TrackList->selectionModel()->selectedRows(0))
+        tlist.append(index.row());
 
-    int allblocks = 0;
-    for(int i = 0;i < tracks.length(); i++)
-        allblocks += tracks[i].blockcount();
-
-    uploadDialog->init(tracks.length(), allblocks);
-
-    for(int i = 0;i < tracks.length(); i++)
-    {
-        QString filename, errmsg;
-        QString title = tracks[i].title();
-        if(title.isNull())
-            filename = tr("Track %1").arg(tracks[i].tracknum()+1);
-        else
-            filename = tracks[i].artist() + " - " + title;
-
-        uploadDialog->starttrack(tracks[i], filename);
-        if (!tracks[i].copyprotected())
-        {
-            QString codec = tracks[i].codecname();
-            if (codec == "MPEG")
-            {
-                checkfile(UploadDirectory, filename, ".mp3");
-                errmsg = dumpmp3 (tracks[i], UploadDirectory + "/" + filename + ".mp3");
-                if(errmsg.isNull())
-                    addid3tag (tracks[i].title(),tracks[i].artist(),tracks[i].album(), UploadDirectory+ "/" +filename + ".mp3");
-            }
-            else if (codec == "LPCM")
-            {
-                checkfile(UploadDirectory, filename, ".wav");
-                errmsg = dumppcm (tracks[i], UploadDirectory + "/" + filename + ".wav");
-            }
-            else if (codec == "AT3+" || codec == "AT3 ")
-            {
-                checkfile(UploadDirectory, filename, ".oma");
-                errmsg = dumpoma (tracks[i], UploadDirectory + "/" + filename + ".oma");
-            }
-        }
-        else
-            errmsg = tr("upload disabled because of DRM encryption");
-
-        if(errmsg.isNull())
-            uploadDialog->trackSucceeded();
-        else
-            uploadDialog->trackFailed(errmsg);
-
-        QApplication::processEvents();
-        if(uploadDialog->upload_canceled())
-            break;
-    }
-    uploadDialog->finished();
-
-    emit himd_idle(ui->himdpath->text());
+    current_device->batchUpload(tlist, UploadDirectory);
 }
 
 QHiMDMainWindow::QHiMDMainWindow(QWidget *parent)
@@ -386,22 +149,25 @@ QHiMDMainWindow::QHiMDMainWindow(QWidget *parent)
 {
     aboutDialog = new QHiMDAboutDialog;
     formatDialog = new QHiMDFormatDialog;
-    uploadDialog = new QHiMDUploadDialog;
+    current_device = NULL;
     detect = createDetection(this);
     ui->setupUi(this);
     ui->updir->setText(settings.value("lastUploadDirectory",
                                          QDir::homePath()).toString());
     set_buttons_enable(1,0,0,0,0,0,1);
-    init_himd_browser();
+    init_himd_browser(&htmodel);   // use himd tracks model as default
     init_local_browser();
     read_window_settings();
-    ui->himd_devices->hide();
+    ui->himdpath->hide();
     if(!autodetect_init())
         ui->statusBar->showMessage(" autodetection disabled", 10000);
 }
 
 QHiMDMainWindow::~QHiMDMainWindow()
 {
+    if(current_device && current_device->isOpen())
+        current_device->close();
+
     save_window_settings();
     delete ui;
 }
@@ -454,8 +220,9 @@ void QHiMDMainWindow::on_action_Format_triggered()
 void QHiMDMainWindow::on_action_Connect_triggered()
 {
     int index;
+    QHiMDDevice *dev;
     QString HiMDDirectory;
-    HiMDDirectory = settings.value("lastHiMDDirectory", QDir::rootPath()).toString();
+    HiMDDirectory = settings.value("lastImageDirectory", QDir::rootPath()).toString();
     HiMDDirectory = QFileDialog::getExistingDirectory(this,
                                                  tr("Select directory of HiMD Medium"),
                                                  HiMDDirectory,
@@ -464,21 +231,13 @@ void QHiMDMainWindow::on_action_Connect_triggered()
     if(HiMDDirectory.isEmpty())
         return;
 
-    index = ui->himd_devices->findText(HiMDDirectory);
-    if(index == -1)
-    {
-        ui->himd_devices->addItem(HiMDDirectory);
-        index = ui->himd_devices->findText(HiMDDirectory);
-    }
-    ui->himd_devices->setCurrentIndex(index);
+    index = ui->himd_devices->findText("disc image");
+    ui->himd_devices->setCurrentIndex(index);   // index of disk image device
+    dev = (QHiMDDevice *)ui->himd_devices->itemData(index).value<void *>();
+    dev->setPath(HiMDDirectory);
+    ui->himd_devices->setItemText(index, QString((dev->name() + " at " + dev->path() )));
 
-    if(ui->himd_devices->isHidden())
-    {
-        ui->himd_devices->show();
-        ui->himdpath->hide();
-    }
-
-    open_himd_at(HiMDDirectory);
+    open_device(dev);
 }
 
 void QHiMDMainWindow::on_upload_button_clicked()
@@ -506,67 +265,60 @@ void QHiMDMainWindow::handle_local_selection_change(const QItemSelection&, const
     }
 
     if(localmodel.fileInfo(index).isFile())
-        download_possible = trackmodel.is_open();
+        download_possible = current_device->isOpen();
 
     ui->action_Download->setEnabled(download_possible);
     ui->download_button->setEnabled(download_possible);
 }
 
-void QHiMDMainWindow::himd_found(QString HiMDPath)
+void QHiMDMainWindow::device_list_changed(QMDDevicePtrList dplist)
 {
-    int index;
+    QString device;
+    QMDDevice * dev;
 
-    if(HiMDPath.isEmpty())
-        return;
+    /* close current device if it is removed from device list */
+    if(current_device != NULL && current_device->isOpen() && !dplist.contains(current_device)) {
+        current_device_closed();
+    }
 
-    index = ui->himd_devices->findText(HiMDPath);
-    if(index == -1)
-        ui->himd_devices->addItem(HiMDPath);
+    ui->himd_devices->clear();
 
-    if(ui->himd_devices->isHidden())
+    foreach(dev, dplist)
     {
-        ui->himd_devices->show();
-        ui->himdpath->hide();
+        device = QString(dev->deviceType() == NETMD_DEVICE ? dev->name() : dev->name() + " at " + dev->path() );
+        ui->himd_devices->addItem(device, qVariantFromValue((void *)dev));
     }
 
-    if(!trackmodel.is_open())
-    {
-        index = ui->himd_devices->findText(HiMDPath);
-        ui->himd_devices->setCurrentIndex(index);
-        open_himd_at(HiMDPath);
+    if(current_device)
+        ui->himd_devices->setCurrentIndex(dplist.indexOf(current_device));
+    else {
+        if(dplist.count() > 1) {
+            ui->himd_devices->setCurrentIndex(1);
+            open_device(dplist.at(1));   // open first autodetected device
+        }
     }
-
 }
-
-void QHiMDMainWindow::himd_removed(QString HiMDPath)
+void QHiMDMainWindow::on_himd_devices_activated(QString device)
 {
-    int index;
+    QMDDevice * dev;
+    int index = ui->himd_devices->findText(device);
 
-    if(HiMDPath.isEmpty())
-        return;
-    if (ui->himdpath->text() == HiMDPath)
-    {
-        ui->himdpath->setText(tr("(disconnected)"));
-        ui->statusBar->clearMessage();
-        trackmodel.close();
-    }
-
-    index = ui->himd_devices->findText(HiMDPath);
-    if(index != -1)
-    {
-        ui->himd_devices->removeItem(index);
-    }
+    dev = (QMDDevice *)ui->himd_devices->itemData(index).value<void *>();
 
-    if(ui->himd_devices->count() == 0)
-    {
-        ui->himd_devices->hide();
-        ui->himdpath->show();
-    }
+    open_device(dev);
 }
 
-void QHiMDMainWindow::on_himd_devices_activated(QString device)
+void QHiMDMainWindow::current_device_closed()
 {
-    open_himd_at(device);
+    QObject::disconnect(current_device, SIGNAL(closed()), this, SLOT(current_device_closed()));
+
+    if(current_device->deviceType() == NETMD_DEVICE)
+        ntmodel.close();
+
+    else if(current_device->deviceType() == HIMD_DEVICE)
+        htmodel.close();
+
+    current_device = NULL;
 }
 
 void QHiMDMainWindow::on_download_button_clicked()
diff --git a/qhimdtransfer/qhimdmainwindow.h b/qhimdtransfer/qhimdmainwindow.h
index 85be8ae..c08d465 100644
--- a/qhimdtransfer/qhimdmainwindow.h
+++ b/qhimdtransfer/qhimdmainwindow.h
@@ -6,14 +6,9 @@
 #include <QtCore/QSettings>
 #include "qhimdaboutdialog.h"
 #include "qhimdformatdialog.h"
-#include "qhimduploaddialog.h"
 #include "qhimddetection.h"
-#include "qhimdmodel.h"
-#include "../libhimd/himd.h"
-#include <tlist.h>
-#include <fileref.h>
-#include <tfile.h>
-#include <tag.h>
+#include "qmdmodel.h"
+#include "qmddevice.h"
 
 extern "C" {
 #include <sox.h>
@@ -36,22 +31,20 @@ private:
     Ui::QHiMDMainWindowClass *ui;
     QHiMDAboutDialog * aboutDialog;
     QHiMDFormatDialog * formatDialog;
-    QHiMDUploadDialog * uploadDialog;
     QHiMDDetection * detect;
-    QHiMDTracksModel trackmodel;
+    QNetMDTracksModel ntmodel;
+    QHiMDTracksModel htmodel;
     QHiMDFileSystemModel localmodel;
     QSettings settings;
-    QString dumpmp3(const QHiMDTrack & trk, QString file);
-    QString dumpoma(const QHiMDTrack & trk, QString file);
-    QString dumppcm(const QHiMDTrack & trk, QString file);
-    void checkfile(QString UploadDirectory, QString &filename, QString extension);
+    QMDDevice * current_device;
     void set_buttons_enable(bool connect, bool download, bool upload, bool rename, bool del, bool format, bool quit);
-    void init_himd_browser();
+    void init_himd_browser(QAbstractListModel *model);
     void init_local_browser();
     void save_window_settings();
     void read_window_settings();
     bool autodetect_init();
-    void open_himd_at(const QString & path);
+    void setCurrentDevice(QMDDevice * dev);
+    void open_device(QMDDevice * dev);
     void upload_to(const QString & path);
 
 private slots:
@@ -64,14 +57,11 @@ private slots:
     void on_upload_button_clicked();
     void handle_himd_selection_change(const QItemSelection&, const QItemSelection&);
     void handle_local_selection_change(const QItemSelection&, const QItemSelection&);
-    void himd_found(QString path);
-    void himd_removed(QString path);
+    void device_list_changed(QMDDevicePtrList dplist);
     void on_himd_devices_activated(QString device);
+    void current_device_closed();
     void on_download_button_clicked();
 
-signals:
-    void himd_busy(QString path);
-    void himd_idle(QString path);
 };
 
 #endif // QHIMDMAINWINDOW_H
diff --git a/qhimdtransfer/qhimdmainwindow.ui b/qhimdtransfer/qhimdmainwindow.ui
index b55845f..f394d92 100644
--- a/qhimdtransfer/qhimdmainwindow.ui
+++ b/qhimdtransfer/qhimdmainwindow.ui
@@ -35,7 +35,7 @@
            </font>
           </property>
           <property name="text">
-           <string>HiMD path</string>
+           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;minidisc device (path):&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
           </property>
          </widget>
         </item>
@@ -52,6 +52,20 @@
        </layout>
       </item>
       <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_4">
+        <item>
+         <widget class="QLabel" name="disc_title_text">
+          <property name="text">
+           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;disc title:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLineEdit" name="DiscTitle"/>
+        </item>
+       </layout>
+      </item>
+      <item>
        <widget class="QTreeView" name="TrackList">
         <property name="editTriggers">
          <set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
@@ -174,7 +188,7 @@
      <x>0</x>
      <y>0</y>
      <width>784</width>
-     <height>18</height>
+     <height>20</height>
     </rect>
    </property>
    <widget class="QMenu" name="menu_Action">
diff --git a/qhimdtransfer/qhimdmodel.cpp b/qhimdtransfer/qhimdmodel.cpp
deleted file mode 100644
index ac4bde5..0000000
--- a/qhimdtransfer/qhimdmodel.cpp
+++ /dev/null
@@ -1,289 +0,0 @@
-#include <QtGui/QFont>
-#include <QtGui/QFontMetrics>
-#include "qhimdmodel.h"
-
-static QString get_himd_str(struct himd * himd, int idx)
-{
-    QString outstr;
-    char * str;
-    if(!idx)
-        return QString();
-    str = himd_get_string_utf8(himd, idx, NULL, NULL);
-    if(!str)
-        return QString();
-
-    outstr = QString::fromUtf8(str);
-    himd_free(str);
-    return outstr;
-}
-
-QHiMDTrack::QHiMDTrack(struct himd * himd, unsigned int trackindex) : himd(himd), trknum(trackindex)
-{
-    trackslot = himd_get_trackslot(himd, trackindex, NULL);
-    if(trackslot != 0)
-        if(himd_get_track_info(himd, trackslot, &ti, NULL) < 0)
-            trackslot = -1;
-}
-
-unsigned int QHiMDTrack::tracknum() const
-{
-    return trknum;
-}
-
-QString QHiMDTrack::title() const
-{
-    if(trackslot != 0)
-        return get_himd_str(himd, ti.title);
-    else
-        return QString();
-}
-
-QString QHiMDTrack::artist() const
-{
-    if(trackslot != 0)
-        return get_himd_str(himd, ti.artist);
-    else
-        return QString();
-}
-
-QString QHiMDTrack::album() const
-{
-    if(trackslot != 0)
-        return get_himd_str(himd, ti.album);
-    else
-        return QString();
-}
-
-QString QHiMDTrack::codecname() const
-{
-    if(trackslot != 0)
-        return himd_get_codec_name(&ti);
-    else
-        return QString();
-}
-
-QTime QHiMDTrack::duration() const
-{
-    QTime t;
-    if(trackslot != 0)
-        return t.addSecs(ti.seconds);
-    else
-        return t;
-}
-
-bool QHiMDTrack::copyprotected() const
-{
-    if(trackslot != 0)
-        return !himd_track_uploadable(himd, &ti);
-    return true;
-}
-
-int QHiMDTrack::blockcount() const
-{
-    if(trackslot != 0)
-        return himd_track_blocks(himd, &ti, NULL);
-    else
-        return 0;
-}
-
-QString QHiMDTrack::openMpegStream(struct himd_mp3stream * str) const
-{
-    struct himderrinfo status;
-    if(himd_mp3stream_open(himd, trackslot, str, &status) < 0)
-        return QString::fromUtf8(status.statusmsg);
-    return QString();    
-}
-
-QString QHiMDTrack::openNonMpegStream(struct himd_nonmp3stream * str) const
-{
-    struct himderrinfo status;
-    if(himd_nonmp3stream_open(himd, trackslot, str, &status) < 0)
-        return QString::fromUtf8(status.statusmsg);
-    return QString();    
-}
-
-QByteArray QHiMDTrack::makeEA3Header() const
-{
-    char header[EA3_FORMAT_HEADER_SIZE];
-    make_ea3_format_header(header, &ti.codec_info);
-    return QByteArray(header,EA3_FORMAT_HEADER_SIZE);
-}
-
-enum columnum {
-  ColId, ColTitle, ColArtist, ColAlbum, ColLength, ColCodec, ColUploadable,
-  LAST_columnnum = ColUploadable
-};
-
-QVariant QHiMDTracksModel::headerData(int section, Qt::Orientation orientation, int role) const
-{
-    if(orientation != Qt::Horizontal)
-        return QVariant();
-
-    if(role == Qt::SizeHintRole)
-    {
-        static QFont f;
-        static QFontMetrics met(f);
-        switch((columnum)section)
-        {
-            case ColId:
-                return QSize(met.width("9999")+5, 0);
-            case ColTitle:
-            case ColArtist:
-            case ColAlbum:
-                return QSize(25*met.averageCharWidth(), 0);
-            case ColLength:
-                return QSize(met.width("9:99:99"), 0);
-            case ColCodec:
-            case ColUploadable:
-                /* Really use the header for the metric in these columns,
-                   contents will be shorter */
-                return QAbstractListModel::headerData(section,orientation,role);
-        }
-    }
-
-    if(role == Qt::DisplayRole)
-    {
-        switch((columnum)section)
-        {
-            case ColId:
-                return tr("Nr.");
-            case ColTitle:
-                return tr("Title");
-            case ColArtist:
-                return tr("Artist");
-            case ColAlbum:
-                return tr("Album");
-            case ColLength:
-                return tr("Length");
-            case ColCodec:
-                return tr("Format");
-            case ColUploadable:
-                return tr("Uploadable");
-        }
-    }
-    return QVariant();
-}
-
-QVariant QHiMDTracksModel::data(const QModelIndex & index, int role) const
-{
-    if(role == Qt::TextAlignmentRole && 
-       (index.column() == ColId || index.column() == ColLength))
-        return Qt::AlignRight;
-
-    if(index.row() >= rowCount())
-        return QVariant();
-
-    QHiMDTrack track(himd, index.row());
-
-    if(role == Qt::CheckStateRole && index.column() == ColUploadable)
-        return track.copyprotected() ? Qt::Unchecked : Qt::Checked;
-
-    if(role == Qt::DisplayRole)
-    {
-        switch((columnum)index.column())
-        {
-            case ColId:
-                return index.row() + 1;
-            case ColTitle:
-                return track.title();
-            case ColArtist:
-                return track.artist();
-            case ColAlbum:
-                return track.album();
-            case ColLength:
-            {
-                QTime t = track.duration();
-                if(t < QTime(1,0,0))
-                    return t.toString("m:ss");
-                else
-                    return t.toString("h:mm:ss");
-            }
-            case ColCodec:
-                return track.codecname();
-            case ColUploadable:
-                return QVariant(); /* Displayed by checkbox */
-        }
-    }
-    return QVariant();
-}
-
-int QHiMDTracksModel::rowCount(const QModelIndex &) const
-{
-    if(himd)
-        return himd_track_count(himd);
-    else
-        return 0;
-}
-
-int QHiMDTracksModel::columnCount(const QModelIndex &) const
-{
-    return LAST_columnnum+1;
-}
-
-QString QHiMDTracksModel::open(const QString & path)
-{
-    struct himd * newhimd;
-    struct himderrinfo status;
-
-    newhimd = new struct himd;
-    if(himd_open(newhimd,path.toUtf8(), &status) < 0)
-    {
-        delete newhimd;
-        return QString::fromUtf8(status.statusmsg);
-    }
-    close();
-    himd = newhimd;
-    reset();	/* inform views that the model contents changed */
-    return QString();
-}
-
-bool QHiMDTracksModel::is_open()
-{
-    return himd != NULL;
-}
-
-void QHiMDTracksModel::close()
-{
-    struct himd * oldhimd;
-    if(!himd)
-        return;
-    oldhimd = himd;
-    himd = NULL;
-    reset();	/* inform views that the model contents changed */
-    himd_close(oldhimd);
-    delete oldhimd;
-}
-
-QHiMDTrack QHiMDTracksModel::track(int trknum) const
-{
-    return QHiMDTrack(himd, trknum);
-}
-
-QHiMDTrackList QHiMDTracksModel::tracks(const QModelIndexList & modelindices) const
-{
-    QHiMDTrackList tracks;
-    QModelIndex index;
-    foreach(index, modelindices)
-        tracks.append(track(index.row()));
-    return tracks;
-}
-
-QStringList QHiMDTracksModel::downloadableFileExtensions() const
-{
-    return (QStringList() << "mp3");
-}
-
-/* QFileSystemModel stuff */
-
-Qt::ItemFlags QHiMDFileSystemModel::flags(const QModelIndex &index) const
-{
-    if(!isDir(index) && !selectableExtensions.contains((fileInfo(index).suffix()), Qt::CaseInsensitive))
-        return Qt::NoItemFlags;   //not selectable, not enabled (grayed out in the browser)
-
-    return QFileSystemModel::flags(index);
-}
-
-void QHiMDFileSystemModel::setSelectableExtensions(QStringList extensions)
-{
-    selectableExtensions = extensions;
-}
diff --git a/qhimdtransfer/qhimdmodel.h b/qhimdtransfer/qhimdmodel.h
deleted file mode 100644
index 6ccc2f8..0000000
--- a/qhimdtransfer/qhimdmodel.h
+++ /dev/null
@@ -1,68 +0,0 @@
-#ifndef QHIMDMODEL_H
-#define QHIMDMODEL_H
-
-#include <QtCore/QAbstractListModel>
-#include <QtCore/QTime>
-#include <QtCore/QList>
-#include <QtCore/QStringList>
-#include <QtGui/QFileSystemModel>
-#include "himd.h"
-
-#include "sony_oma.h"
-
-class QHiMDTracksModel;
-
-class QHiMDTrack {
-    struct himd * himd;
-    unsigned int trknum;
-    unsigned int trackslot;
-    struct trackinfo ti;
-public:
-    QHiMDTrack(struct himd * himd, unsigned int trackindex);
-    unsigned int tracknum() const;
-    QString title() const;
-    QString artist() const;
-    QString album() const;
-    QString codecname() const;
-    QTime duration() const;
-    bool copyprotected() const;
-    int blockcount() const;
-
-    QString openMpegStream(struct himd_mp3stream * str) const;
-    QString openNonMpegStream(struct himd_nonmp3stream * str) const;
-    QByteArray makeEA3Header() const;
-};
-
-typedef QList<QHiMDTrack> QHiMDTrackList;
-
-class QHiMDTracksModel : public QAbstractListModel {
-    Q_OBJECT
-
-    struct himd *himd;
-public:
-    QHiMDTracksModel() : himd(NULL) {}
-    /* QAbstractListModel stuff */
-    virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
-    virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
-    virtual int rowCount(const QModelIndex & parent = QModelIndex() ) const;
-    virtual int columnCount(const QModelIndex & parent = QModelIndex() ) const;
-    /* HiMD containter stuff */
-    QString open(const QString & path);	/* returns null if OK, error message otherwise */
-    bool is_open();
-    void close();
-    QHiMDTrack track(int trackidx) const;
-    QHiMDTrackList tracks(const QModelIndexList & indices) const;
-    QStringList downloadableFileExtensions() const;
-};
-
-class QHiMDFileSystemModel : public QFileSystemModel {
-    Q_OBJECT
-
-    QStringList selectableExtensions;
-public:
-    virtual Qt::ItemFlags flags(const QModelIndex &index) const;
-    void setSelectableExtensions(QStringList extensions);
-};
-
-
-#endif
diff --git a/qhimdtransfer/qhimdtransfer.pro b/qhimdtransfer/qhimdtransfer.pro
index 236b5e9..ad6c396 100644
--- a/qhimdtransfer/qhimdtransfer.pro
+++ b/qhimdtransfer/qhimdtransfer.pro
@@ -49,8 +49,10 @@ HEADERS += qhimdaboutdialog.h \
     qhimdformatdialog.h \
     qhimduploaddialog.h \
     qhimdmainwindow.h \
-    qhimdmodel.h \
-    qhimddetection.h
+    qhimddetection.h \
+    qmdtrack.h \
+    qmdmodel.h \
+    qmddevice.h
 FORMS += qhimdaboutdialog.ui \
     qhimdformatdialog.ui \
     qhimduploaddialog.ui \
@@ -60,8 +62,10 @@ SOURCES += main.cpp \
     qhimdformatdialog.cpp \
     qhimduploaddialog.cpp \
     qhimdmainwindow.cpp \
-    qhimdmodel.cpp \
-    qhimddetection.cpp
+    qhimddetection.cpp \
+    qmdtrack.cpp \
+    qmdmodel.cpp \
+    qmddevice.cpp
 win32:SOURCES += qhimdwindetection.cpp
 else:SOURCES += qhimddummydetection.cpp
 RESOURCES += icons.qrc
@@ -77,6 +81,7 @@ mac:ICON = qhimdtransfer.icns
 win32:TARGET = QHiMDTransfer
 mac:TARGET = QHiMDTransfer
 include(../libhimd/use_libhimd.pri)
+include(../libnetmd/use_libnetmd.prl)
 
 # Installing stuff
 translations.files = $$bracketAll(LANGUAGES, qhimdtransfer_,.qm)
diff --git a/qhimdtransfer/qhimduploaddialog.cpp b/qhimdtransfer/qhimduploaddialog.cpp
index 4df44ca..13f327e 100644
--- a/qhimdtransfer/qhimduploaddialog.cpp
+++ b/qhimdtransfer/qhimduploaddialog.cpp
@@ -42,7 +42,7 @@ void QHiMDUploadDialog::finished()
     return;
 }
 
-void QHiMDUploadDialog::starttrack(const QHiMDTrack & trk, const QString & title)
+void QHiMDUploadDialog::starttrack(const QMDTrack &trk, const QString & title)
 {
     tracknum = trk.tracknum() + 1;
     m_ui->curtrack_label->setText(tr("current track: %1 - %2").arg(tracknum).arg(title));
@@ -64,6 +64,8 @@ void QHiMDUploadDialog::init(int trackcount, int totalblocks)
     allfinished = 0;
     m_ui->AllPBar->setRange(0, allblocks);
     m_ui->AllPBar->reset();
+    // reset "canceled" variable, else if last upload was canceled next upload will be canceled automatically
+    canceled = false;
 
     scount = fcount = 0;
     m_ui->success_text->setText("");
diff --git a/qhimdtransfer/qhimduploaddialog.h b/qhimdtransfer/qhimduploaddialog.h
index 86bcd5c..ec7a4d2 100644
--- a/qhimdtransfer/qhimduploaddialog.h
+++ b/qhimdtransfer/qhimduploaddialog.h
@@ -2,7 +2,7 @@
 #define QHIMDUPLOADDIALOG_H
 
 #include <QtGui/QDialog>
-#include "qhimdmodel.h"
+#include "qmdtrack.h"
 
 namespace Ui {
     class QHiMDUploadDialog;
@@ -17,7 +17,7 @@ public:
     bool upload_canceled() { return canceled; }
 
     void init(int trackcount, int totalblocks);
-    void starttrack(const QHiMDTrack & trk, const QString & title);
+    void starttrack(const QMDTrack & trk, const QString & title);
     void blockTransferred();
     void trackFailed(const QString & errmsg);
     void trackSucceeded();
diff --git a/qhimdtransfer/qhimdwindetection.cpp b/qhimdtransfer/qhimdwindetection.cpp
index 1dc437a..db723ef 100644
--- a/qhimdtransfer/qhimdwindetection.cpp
+++ b/qhimdtransfer/qhimdwindetection.cpp
@@ -3,8 +3,6 @@
 #include <QtGui/QWidget>
 #include "qhimddetection.h"
 
-#define WINVER 0x0500
-
 #include <windows.h>
 #include <dbt.h>
 #include <setupapi.h>
@@ -12,10 +10,10 @@
 #include <ddk/ntddstor.h>   // needed for handling storage devices
 #include <ddk/cfgmgr32.h>   // needed for CM_Get_Child function
 
-struct win_himd_device : himd_device {
-                    HANDLE devhandle;
-                    HDEVNOTIFY himdChange;
-                    };
+struct win_os_data : os_data {
+                HANDLE devhandle;
+                HDEVNOTIFY himdChange;
+                };
 
 static const GUID my_GUID_IO_MEDIA_ARRIVAL =
     {0xd07433c0, 0xa98e, 0x11d2, {0x91, 0x7a, 0x00, 0xa0, 0xc9, 0x06, 0x8f, 0xf3} };
@@ -23,6 +21,9 @@ static const GUID my_GUID_IO_MEDIA_ARRIVAL =
 static const GUID my_GUID_IO_MEDIA_REMOVAL =
     {0xd07433c1, 0xa98e, 0x11d2, {0x91, 0x7a, 0x00, 0xa0, 0xc9, 0x06, 0x8f, 0xf3} };
 
+static const GUID my_GUID_DEVINTERFACE_USB_DEVICE =
+    {0xa5dcbf10, 0x6530, 0x11d2, {0x90, 0x1f, 0x00, 0xc0,0x4f, 0xb9, 0x51, 0xed} };
+
 static const int my_DBT_CUSTOMEVENT = 0x8006;
 
 
@@ -37,18 +38,20 @@ public:
     void scan_for_himd_devices();
     QHiMDWinDetection(QObject * parent = NULL);
     ~QHiMDWinDetection();
-    win_himd_device *find_by_path(QString path);
+    //win_himd_device *find_by_path(QString path);
 
 private:
     HDEVNOTIFY hDevNotify;
-    win_himd_device *find_by_handle(HANDLE devhandle);
-    win_himd_device *win_dev_at(int idx);
+    HDEVNOTIFY listen_usbdev;
+    QMDDevice *find_by_handle(HANDLE devhandle);
     void add_himddevice(QString path, QString name);
-    void remove_himddevice(QString path);
+    virtual void remove_himddevice(QString path);
     void add_himd(HANDLE devhandle);
     void remove_himd(HANDLE devhandle);
     HDEVNOTIFY register_mediaChange(HANDLE devhandle);
     void unregister_mediaChange(HDEVNOTIFY himd_change);
+    HDEVNOTIFY register_usbDeviceNotification();
+    void unregister_usbDeviceNotification();
     bool winEvent(MSG * msg, long * result);
 };
 
@@ -58,17 +61,21 @@ QHiMDDetection * createDetection(QObject * parent)
     return new QHiMDWinDetection(parent);
 }
 
-QHiMDWinDetection::QHiMDWinDetection(QObject * parent) 
+QHiMDWinDetection::QHiMDWinDetection(QObject * parent)
   : QHiMDDetection(parent), QWidget(0)
 {
     // ask for Window ID to have Qt create the window.
     (void)winId();
+    // register for usb device notifications
+    if((listen_usbdev = register_usbDeviceNotification()) == NULL)
+        qDebug() << "cannot register usb device notifications" << endl;
 }
 
 QHiMDWinDetection::~QHiMDWinDetection()
 {
-    while (!device_list.isEmpty())
-        remove_himddevice(device_list.at(0)->path);
+    unregister_usbDeviceNotification();
+    clearDeviceList();
+    cleanup_netmd_list();
 }
 
 void QHiMDWinDetection::scan_for_himd_devices()
@@ -96,21 +103,19 @@ void QHiMDWinDetection::scan_for_himd_devices()
     return;
 }
 
-win_himd_device *QHiMDWinDetection::win_dev_at(int idx)
-{
-    return static_cast<win_himd_device*>(device_list.at(idx));
-}
-
-win_himd_device *QHiMDWinDetection::find_by_path(QString path)
+QMDDevice *QHiMDWinDetection::find_by_handle(HANDLE devhandle)
 {
-    return static_cast<win_himd_device*>(QHiMDDetection::find_by_path(path));
-}
+    QMDDevice *mddev;
+    win_os_data *osd;
 
-win_himd_device *QHiMDWinDetection::find_by_handle(HANDLE devhandle)
-{
-    for (int i = 0; i < device_list.size(); i++)
-        if(win_dev_at(i)->devhandle == devhandle)
-            return win_dev_at(i);
+    foreach(mddev, dlist)
+    {
+        if(mddev->deviceType() != HIMD_DEVICE)
+            continue;
+        osd = static_cast<win_os_data *>(mddev->pdata());
+        if(osd->devhandle == devhandle)
+            return mddev;
+    }
 
     return NULL;
 }
@@ -178,6 +183,7 @@ static bool identified(QString devpath, QString & name)
     int vid = devpath.mid(devpath.indexOf("VID") + 4, 4).toInt(NULL,16);
     int pid = devpath.mid(devpath.indexOf("PID") + 4, 4).toInt(NULL,16);
     const char * devname = identify_usb_device(vid, pid);
+
     if (devname)
     {
         name = devname;
@@ -191,7 +197,8 @@ void QHiMDWinDetection::add_himddevice(QString path, QString name)
     if (find_by_path(path))
         return;
 
-    win_himd_device * new_device = new win_himd_device;
+    QHiMDDevice * new_device = new QHiMDDevice();
+    struct win_os_data pdata;
     int k;
     char drv[] = "\\\\.\\X:";
     QByteArray device = "\\\\.\\PHYSICALDRIVE";
@@ -213,80 +220,93 @@ void QHiMDWinDetection::add_himddevice(QString path, QString name)
     if(k != 0)
         device.append(QString::number(sdn.DeviceNumber));
 
-    new_device->devhandle = CreateFileA(device.data(), NULL , FILE_SHARE_READ, NULL,
+
+    pdata.devhandle = CreateFileA(device.data(), NULL , FILE_SHARE_READ, NULL,
                                            OPEN_EXISTING, 0, NULL);
-    if(new_device->devhandle == INVALID_HANDLE_VALUE)
+    if(pdata.devhandle == INVALID_HANDLE_VALUE)
         return;
 
-    new_device->himdChange = register_mediaChange(new_device->devhandle);
-    new_device->is_busy = false;
-    new_device->path = path;
-    new_device->recorder_name = name;
+    pdata.himdChange = register_mediaChange(pdata.devhandle);
+    new_device->setBusy(false);
+    new_device->setPath(path);
+    new_device->setName(name);
 
     file[0] = path.at(0).toAscii();
     if(OpenFile(file, &OFfile, OF_EXIST) != HFILE_ERROR)
-    {
-        new_device->md_inserted = true;
-        emit himd_found(new_device->path);
-        qDebug() << "himd device at " + new_device->path + " added (" + new_device->recorder_name + ")";
-    }
+        pdata.md_inserted = true;
     else
-    {
-        qDebug() << "himd device at " + new_device->path + " added (" + new_device->recorder_name + ")" + " ; without MD";
-        new_device->md_inserted = false;
-    }
+        pdata.md_inserted = false;
 
-    device_list.append(new_device);
+    new_device->setPlatformData(pdata);
+    dlist.append(new_device);
+    emit deviceListChanged(dlist);
 
     return;
-
 }
 
 void QHiMDWinDetection::remove_himddevice(QString path)
 {
-    win_himd_device * dev = find_by_path(path);
+    int index = -1;
+    win_os_data * data;
+    QHiMDDevice * dev = static_cast<QHiMDDevice *>(find_by_path(path));
+
     if (!dev)
         return;
 
-    unregister_mediaChange(dev->himdChange);
+    index = dlist.indexOf(dev);
 
-    if (dev->devhandle != NULL)
-        CloseHandle(dev->devhandle);
+    if(dev->isOpen())
+        dev->close();
 
-    emit himd_removed(dev->path);
+    if(dev->name() != "disc image") {
+        data = static_cast<win_os_data *>(dev->pdata());
+        if(data->himdChange != NULL)
+            unregister_mediaChange(data->himdChange);
 
-    qDebug() << "himd device at " + dev->path + " removed (" + dev->recorder_name + ")";
+        if(data->devhandle != NULL)
+            CloseHandle(data->devhandle);
+    }
 
-    device_list.removeAll(dev);
     delete dev;
+    dev = NULL;
+
+    dlist.removeAt(index);
+
+    emit deviceListChanged(dlist);
 }
 
 void QHiMDWinDetection::add_himd(HANDLE devhandle)
 {
-    win_himd_device * dev = find_by_handle(devhandle);
+    win_os_data * data;
+    QMDDevice * dev = find_by_handle(devhandle);
     if (!dev)
         return;
 
-    if(!dev->md_inserted)
+    data = static_cast<win_os_data *>(dev->pdata());
+
+    if(!data->md_inserted)
     {
-        dev->md_inserted = true;
-        emit himd_found(dev->path);
-        qDebug() << "himd device at " + dev->path + " : md inserted";
+        data->md_inserted = true;
     }
     return;
 }
 
 void QHiMDWinDetection::remove_himd(HANDLE devhandle)
 {
-    win_himd_device * dev = find_by_handle(devhandle);
+    win_os_data * data;
+    QMDDevice * dev = find_by_handle(devhandle);
+
     if (!dev)
         return;
 
-    if(dev->md_inserted)
+    data = static_cast<win_os_data *>(dev->pdata());
+
+    if(dev->isOpen())
+        dev->close();
+
+    if(data->md_inserted)
     {
-        dev->md_inserted = false;
-        emit himd_removed(dev->path);
-        qDebug() << "himd device at " + dev->path + " :  md removed";
+        data->md_inserted = false;
     }
     return;
 }
@@ -311,9 +331,30 @@ void QHiMDWinDetection::unregister_mediaChange(HDEVNOTIFY himd_change)
         UnregisterDeviceNotification(himd_change);
 }
 
+HDEVNOTIFY QHiMDWinDetection::register_usbDeviceNotification()
+{
+    DEV_BROADCAST_DEVICEINTERFACE filter;
+
+    ZeroMemory(&filter, sizeof(filter));
+    filter.dbcc_size = sizeof(filter);
+    filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+    filter.dbcc_reserved = 0;
+    filter.dbcc_classguid = my_GUID_DEVINTERFACE_USB_DEVICE;
+
+    return RegisterDeviceNotification( this->winId(), &filter, DEVICE_NOTIFY_WINDOW_HANDLE);
+
+}
+
+void QHiMDWinDetection::unregister_usbDeviceNotification()
+{
+    if(listen_usbdev != NULL)
+       UnregisterDeviceNotification(listen_usbdev);
+}
+
 bool QHiMDWinDetection::winEvent(MSG * msg, long * result)
     {
         QString name, devID, path ;
+
         if(msg->message == WM_DEVICECHANGE)
         {
             PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR )msg->lParam;
@@ -335,6 +376,16 @@ bool QHiMDWinDetection::winEvent(MSG * msg, long * result)
                             }
                         }
                     }
+                    else if(pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
+                    {
+                         PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
+                         devID = QString::fromWCharArray(pDevInf->dbcc_name).toUpper();
+                         /* only handle netmd devices, himd devices will be handled by DBT_DEVTYP_VOLUME */
+                         if(identified(devID, name) && name.contains("NetMD)")) {
+                             qDebug() << name << " detected, rescanning netmd devices" << endl;
+                             rescan_netmd_devices();
+                         }
+                    }
                     break;
                 }
                 case DBT_DEVICEREMOVECOMPLETE :
@@ -346,6 +397,15 @@ bool QHiMDWinDetection::winEvent(MSG * msg, long * result)
                         qDebug() << "Message:DBT_DEVICEREMOVECOMPLETE for drive " + path;
                         remove_himddevice(path);
                     }
+                    else if(pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
+                    {
+                         PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
+                         devID = QString::fromWCharArray(pDevInf->dbcc_name).toUpper();
+                         if(identified(devID, name) && name.contains("NetMD)")) {
+                             qDebug() << name << " removed, rescanning netmd devices" << endl;
+                             rescan_netmd_devices();
+                         }
+                    }
                     break;
                 }
                 case DBT_DEVICEQUERYREMOVE :
@@ -353,13 +413,13 @@ bool QHiMDWinDetection::winEvent(MSG * msg, long * result)
                     if(pHdr->dbch_devicetype & DBT_DEVTYP_HANDLE)
                     {
                         PDEV_BROADCAST_HANDLE pHdrh = (PDEV_BROADCAST_HANDLE)pHdr;
-                        win_himd_device *dev = find_by_handle(pHdrh->dbch_handle);
+                        QMDDevice *dev = find_by_handle(pHdrh->dbch_handle);
                         if(!dev)
                         {
                             qDebug() << "Message:DBT_DEVICEQUERYREMOVE for unknown device " << pHdrh->dbch_handle;
                             break;
                         }
-                        if(dev->is_busy)
+                        if(dev->isBusy())
                         {
                             *result = BROADCAST_QUERY_DENY;
                             qDebug() << "Message:DBT_DEVICEQUERYREMOVE for drive " + path + " denied: transfer in progress";
@@ -368,9 +428,24 @@ bool QHiMDWinDetection::winEvent(MSG * msg, long * result)
                         else
                         {
                             qDebug() << "Message:DBT_DEVICEQUERYREMOVE requested";
-                            remove_himddevice(dev->path);
+                            remove_himddevice(dev->path());
                         }
                     }
+                    else if(pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
+                    {
+                         PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
+                         devID = QString::fromWCharArray(pDevInf->dbcc_name).toUpper();
+                         if(identified(devID, name) && name.contains("NetMD)")) {
+                             QMDDevice * dev = find_by_name(name);
+                             if(!dev)
+                                 break;
+                             if(dev->isBusy()) {
+                                 *result = BROADCAST_QUERY_DENY;
+                                 return true;
+                             }
+                             dev->close();
+                         }
+                    }
                     break;
                 }
                 case my_DBT_CUSTOMEVENT :
diff --git a/qhimdtransfer/qmddevice.cpp b/qhimdtransfer/qmddevice.cpp
new file mode 100644
index 0000000..eeb7e78
--- /dev/null
+++ b/qhimdtransfer/qmddevice.cpp
@@ -0,0 +1,480 @@
+#include <qmddevice.h>
+#include <QtGui/QMessageBox>
+#include <QtGui/QApplication>
+#include <QFile>
+#include <tlist.h>
+#include <fileref.h>
+#include <tfile.h>
+#include <tag.h>
+
+extern "C" {
+#include <sox.h>
+}
+
+/* common device members */
+QMDDevice::QMDDevice() : dev_type(NO_DEVICE)
+{
+}
+
+QMDDevice::~QMDDevice()
+{
+    close();
+}
+
+enum device_type QMDDevice::deviceType()
+{
+    return dev_type;
+}
+
+void QMDDevice::setPath(QString path)
+{
+    device_path = path;
+}
+
+QString QMDDevice::path()
+{
+    return device_path;
+}
+
+void QMDDevice::setName(QString name)
+{
+    recorder_name = name;
+}
+
+QString QMDDevice::name()
+{
+    return recorder_name;
+}
+
+void QMDDevice::setBusy(bool busy)
+{
+    is_busy = busy;
+}
+
+bool QMDDevice::isBusy()
+{
+    return is_busy;
+}
+
+void QMDDevice::setPlatformData(os_data pdata)
+{
+    osdata = pdata;
+}
+
+os_data * QMDDevice::pdata()
+{
+    os_data * data = &osdata;
+    return data;
+}
+
+QStringList QMDDevice::downloadableFileExtensions() const
+{
+    if(dev_type == NETMD_DEVICE)
+        return QStringList() << "wav";
+
+    if(dev_type == HIMD_DEVICE)
+        return QStringList() << "mp3";
+
+    return QStringList();
+}
+
+void QMDDevice::checkfile(QString UploadDirectory, QString &filename, QString extension)
+{
+    QFile f;
+    QString newname;
+    int i = 2;
+
+    f.setFileName(UploadDirectory + "/" + filename + extension);
+    while(f.exists())
+    {
+        newname = filename + " (" + QString::number(i) + ")";
+        f.setFileName(UploadDirectory + "/" + newname + extension);
+        i++;
+    }
+    if(!newname.isEmpty())
+        filename = newname;
+}
+
+
+/* netmd device members */
+QNetMDDevice::QNetMDDevice()
+{
+    dev_type = NETMD_DEVICE;
+    devh = NULL;
+    netmd = NULL;
+    is_open = false;
+}
+
+QNetMDDevice::~QNetMDDevice()
+{
+    close();
+}
+
+void QNetMDDevice::setUsbDevice(netmd_device * dev)
+{
+    netmd = dev;
+}
+
+QString QNetMDDevice::open()
+{
+    uint8_t i = 0;
+    netmd_error error;
+    char buffer[256];
+
+    if(!netmd)
+        return tr("netmd_device not set, use setUsbDevice(...) function first");
+
+    if((error = netmd_open(netmd, &devh)) != NETMD_NO_ERROR)
+        return tr("Error opening netmd: %1").arg(netmd_strerror(error));
+
+    netmd_initialize_disc_info(devh, &current_md);
+
+    /* generate track count first, needed by QNetMDTracksModel */
+    while(netmd_request_title(devh, i, buffer, sizeof(buffer)) >= 0)
+        i++;
+
+    trk_count = i;
+
+    is_open = true;
+    emit opened();
+    return QString();
+}
+
+void QNetMDDevice::close()
+{
+    if(!devh)
+        return;
+
+    netmd_clean_disc_info(&current_md);
+    netmd_close(devh);
+    devh = NULL;
+
+    is_open = false;
+    trk_count = 0;
+    emit closed();
+}
+
+QString QNetMDDevice::discTitle()
+{
+    return QString(current_md.groups[0].name);
+}
+
+QNetMDTrack QNetMDDevice::netmdTrack(unsigned int trkindex)
+{
+    minidisc * disc = &current_md;
+
+    return QNetMDTrack(devh, disc, trkindex);
+}
+
+void QNetMDDevice::batchUpload(QMDTrackIndexList tlist, QString path)
+{
+    QMessageBox mdStatus;
+
+    mdStatus.setText(tr("netmd uploads are not implemented, yet\n will be comming soon"));
+    mdStatus.exec();
+}
+
+/* himd device members */
+
+QHiMDDevice::QHiMDDevice()
+{
+    dev_type = HIMD_DEVICE;
+    himd = NULL;
+    is_open = false;
+}
+
+QHiMDDevice::~QHiMDDevice()
+{
+    close();
+}
+
+QString QHiMDDevice::open()
+{
+    struct himderrinfo status;
+
+    if(!pdata()->md_inserted)
+        return tr("cannot open device, no disc");
+
+    if(himd)
+        return QString();  // already opened
+
+    himd = new struct himd;
+    if(himd_open(himd, device_path.toUtf8(), &status) < 0)
+    {
+        delete himd;
+        himd = NULL;
+        return QString::fromUtf8(status.statusmsg);
+    }
+
+    trk_count = himd_track_count(himd);
+    is_open = true;
+    emit opened();
+    return QString();
+}
+
+void QHiMDDevice::close()
+{
+    if(!himd)
+        return;
+
+    himd_close(himd);
+    delete himd;
+    himd = NULL;
+
+    is_open = false;
+    trk_count = 0;
+    emit closed();
+}
+
+QHiMDTrack QHiMDDevice::himdTrack(unsigned int trkindex)
+{
+    return QHiMDTrack(himd, trkindex);
+}
+
+QString QHiMDDevice::dumpmp3(const QHiMDTrack &trk, QString file)
+{
+    QString errmsg;
+    struct himd_mp3stream str;
+    struct himderrinfo status;
+    unsigned int len;
+    const unsigned char * data;
+    QFile f(file);
+
+    if(!f.open(QIODevice::ReadWrite))
+    {
+        return tr("Error opening file for MP3 output");
+    }
+    if(!(errmsg = trk.openMpegStream(&str)).isNull())
+    {
+        f.remove();
+        return tr("Error opening track: ") + errmsg;
+    }
+    while(himd_mp3stream_read_block(&str, &data, &len, NULL, &status) >= 0)
+    {
+        if(f.write((const char*)data,len) == -1)
+        {
+            errmsg = tr("Error writing audio data");
+            goto clean;
+        }
+        uploadDialog.blockTransferred();
+        QApplication::processEvents();
+        if(uploadDialog.upload_canceled())
+        {
+            errmsg = tr("upload aborted by the user");
+            goto clean;
+        }
+
+    }
+    if(status.status != HIMD_STATUS_AUDIO_EOF)
+        errmsg = tr("Error reading audio data: ") + status.statusmsg;
+
+clean:
+    f.close();
+    himd_mp3stream_close(&str);
+    if(!errmsg.isNull())
+        f.remove();
+    return errmsg;
+}
+
+static inline TagLib::String QStringToTagString(const QString & s)
+{
+    return TagLib::String(s.toUtf8().data(), TagLib::String::UTF8);
+}
+
+static void addid3tag(QString title, QString artist, QString album, QString file)
+{
+#ifdef Q_OS_WIN
+    TagLib::FileRef f(file.toStdWString().c_str());
+#else
+    TagLib::FileRef f(file.toUtf8().data());
+#endif
+    TagLib::Tag *t = f.tag();
+    t->setTitle(QStringToTagString(title));
+    t->setArtist(QStringToTagString(artist));
+    t->setAlbum(QStringToTagString(album));
+    t->setComment("*** imported from HiMD via QHiMDTransfer ***");
+    f.file()->save();
+}
+
+QString QHiMDDevice::dumpoma(const QHiMDTrack &track, QString file)
+{
+    QString errmsg;
+    struct himd_nonmp3stream str;
+    struct himderrinfo status;
+    unsigned int len;
+    const unsigned char * data;
+    QFile f(file);
+
+    if(!f.open(QIODevice::ReadWrite))
+        return tr("Error opening file for ATRAC output");
+
+    if(!(errmsg = track.openNonMpegStream(&str)).isNull())
+    {
+        f.remove();
+        return tr("Error opening track: ") + status.statusmsg;
+    }
+
+    if(f.write(track.makeEA3Header()) == -1)
+    {
+        errmsg = tr("Error writing header");
+        goto clean;
+    }
+    while(himd_nonmp3stream_read_block(&str, &data, &len, NULL, &status) >= 0)
+    {
+        if(f.write((const char*)data,len) == -1)
+        {
+            errmsg = tr("Error writing audio data");
+            goto clean;
+        }
+        uploadDialog.blockTransferred();
+        QApplication::processEvents();
+        if(uploadDialog.upload_canceled())
+        {
+            errmsg = QString("upload aborted by the user");
+            goto clean;
+        }
+    }
+    if(status.status != HIMD_STATUS_AUDIO_EOF)
+        errmsg = QString("Error reading audio data: ") + status.statusmsg;
+
+clean:
+    f.close();
+    himd_nonmp3stream_close(&str);
+
+    if(!errmsg.isNull())
+        f.remove();
+    return errmsg;
+}
+
+QString QHiMDDevice::dumppcm(const QHiMDTrack &track, QString file)
+{
+    struct himd_nonmp3stream str;
+    struct himderrinfo status;
+    unsigned int len, i;
+    int left, right;
+    int clipcount;
+    QString errmsg;
+    QFile f(file);
+    const unsigned char * data;
+    sox_format_t * out;
+    sox_sample_t soxbuf [HIMD_MAX_PCMFRAME_SAMPLES * 2];
+    sox_signalinfo_t signal_out;
+
+    signal_out.channels = 2;
+    signal_out.length = 0;
+    signal_out.precision = 16;
+    signal_out.rate = 44100;
+
+    if(!(out = sox_open_write(file.toUtf8(), &signal_out, NULL, NULL, NULL, NULL)))
+        return tr("Error opening file for WAV output");
+
+    if(!(errmsg = track.openNonMpegStream(&str)).isNull())
+    {
+        f.remove();
+        return tr("Error opening track: ") + status.statusmsg;
+    }
+
+    while(himd_nonmp3stream_read_block(&str, &data, &len, NULL, &status) >= 0)
+    {
+
+      for(i = 0; i < len/4; i++) {
+
+        left = data[i*4]*256+data[i*4+1];
+        right = data[i*4+2]*256+data[i*4+3];
+        if (left > 0x8000) left -= 0x10000;
+        if (right > 0x8000) right -= 0x10000;
+
+        soxbuf[i*2] = SOX_SIGNED_16BIT_TO_SAMPLE(left, clipcount);
+        soxbuf[i*2+1] = SOX_SIGNED_16BIT_TO_SAMPLE(right, clipcount);
+        (void)clipcount; /* suppess "is unused" warning */
+      }
+
+      if (sox_write(out, soxbuf, len/2) != len/2)
+      {
+            errmsg = tr("Error writing audio data");
+            goto clean;
+      }
+      uploadDialog.blockTransferred();
+      QApplication::processEvents();
+      if(uploadDialog.upload_canceled())
+      {
+            errmsg = QString("upload aborted by the user");
+            goto clean;
+      }
+    }
+    if(status.status != HIMD_STATUS_AUDIO_EOF)
+        errmsg = QString("Error reading audio data: ") + status.statusmsg;
+
+clean:
+    sox_close(out);
+    himd_nonmp3stream_close(&str);
+
+    if(!errmsg.isNull())
+        f.remove();
+    return errmsg;
+}
+
+void QHiMDDevice::upload(unsigned int trackidx, QString path)
+{
+    QString filename, errmsg;
+    QHiMDTrack track = himdTrack(trackidx);
+    QString title = track.title();
+
+    if(title.isNull())
+        filename = tr("Track %1").arg(track.tracknum()+1);
+    else
+        filename = track.artist() + " - " + title;
+
+    uploadDialog.starttrack(track, filename);
+    if (!track.copyprotected())
+    {
+        QString codec = track.codecname();
+        if (codec == "MPEG")
+        {
+            checkfile(path, filename, ".mp3");
+            errmsg = dumpmp3 (track, path + "/" + filename + ".mp3");
+            if(errmsg.isNull())
+                addid3tag (track.title(),track.artist(),track.album(), path + "/" +filename + ".mp3");
+        }
+        else if (codec == "LPCM")
+        {
+            checkfile(path, filename, ".wav");
+            errmsg = dumppcm (track, path + "/" + filename + ".wav");
+        }
+        else if (codec == "AT3+" || codec == "AT3 ")
+        {
+            checkfile(path, filename, ".oma");
+            errmsg = dumpoma (track, path + "/" + filename + ".oma");
+        }
+    }
+    else
+        errmsg = tr("upload disabled because of DRM encryption");
+
+    if(errmsg.isNull())
+        uploadDialog.trackSucceeded();
+    else
+        uploadDialog.trackFailed(errmsg);
+
+}
+
+void QHiMDDevice::batchUpload(QMDTrackIndexList tlist, QString path)
+{
+    int allblocks = 0;
+
+    setBusy(true);
+
+    for(int i = 0;i < tlist.length(); i++)
+        allblocks += himdTrack(tlist.at(i)).blockcount();
+
+    uploadDialog.init(tlist.length(), allblocks);
+
+    for(int i = 0; i < tlist.length(); i++) {
+        upload(tlist[i], path);
+        QApplication::processEvents();
+        if(uploadDialog.upload_canceled())
+            break;
+    }
+
+    uploadDialog.finished();
+    setBusy(false);
+}
diff --git a/qhimdtransfer/qmddevice.h b/qhimdtransfer/qmddevice.h
new file mode 100644
index 0000000..2e92102
--- /dev/null
+++ b/qhimdtransfer/qmddevice.h
@@ -0,0 +1,98 @@
+#ifndef QMDDEVICE_H
+#define QMDDEVICE_H
+
+#include <QString>
+#include <QStringList>
+#include <QMetaType>
+
+#include <qmdtrack.h>
+#include "qhimduploaddialog.h"
+
+enum device_type {
+    NO_DEVICE,
+    NETMD_DEVICE,
+    HIMD_DEVICE
+};
+
+struct os_data {
+    bool md_inserted;
+    virtual ~os_data(){}
+};
+
+class QMDDevice : public QObject {
+    Q_OBJECT
+    Q_DISABLE_COPY(QMDDevice)
+
+    QString recorder_name;
+    bool is_busy;
+    struct os_data osdata;
+protected:
+    QString device_path;
+    enum device_type dev_type;
+    bool is_open;
+    unsigned int trk_count;
+    QHiMDUploadDialog uploadDialog;
+public:
+    explicit QMDDevice();  //
+    virtual ~QMDDevice();
+    virtual enum device_type deviceType();  //
+    virtual void setPath(QString path);  //
+    virtual QString path();  //
+    virtual void setName(QString name);  //
+    virtual QString name();  //
+    virtual void setBusy(bool busy);  //
+    virtual bool isBusy();  //
+    virtual QString open() {return QString();}
+    virtual void close() {}
+    virtual bool isOpen() {return is_open;}
+    virtual QString discTitle() {return QString();}
+    virtual void setPlatformData(struct os_data pdata);  //
+    virtual struct os_data * pdata();  //
+    virtual QMDTrack track(unsigned int trkindex) {return QMDTrack();}
+    virtual int trackCount() {return trk_count;}
+    virtual QStringList downloadableFileExtensions() const;  //
+    virtual void checkfile(QString UploadDirectory, QString &filename, QString extension);
+    virtual void batchUpload(QMDTrackIndexList tlist, QString path) {}
+    virtual void upload(unsigned int trackidx, QString path) {}
+
+signals:
+    void opened();
+    void closed();
+};
+
+class QNetMDDevice : public QMDDevice {
+
+    netmd_device * netmd;
+    netmd_dev_handle * devh;
+    minidisc current_md;
+public:
+    explicit QNetMDDevice();
+    virtual ~QNetMDDevice();
+    virtual void setUsbDevice(netmd_device * dev);  //
+    virtual QString open();  //
+    virtual void close();  //
+    virtual QString discTitle();  //
+    virtual QNetMDTrack netmdTrack(unsigned int trkindex);
+    virtual void batchUpload(QMDTrackIndexList tlist, QString path);
+    virtual void upload(unsigned int trackidx, QString path) {}
+
+};
+
+class QHiMDDevice : public QMDDevice {
+
+    struct himd * himd;
+public:
+    explicit QHiMDDevice();
+    virtual ~QHiMDDevice();
+    virtual QString open();  //
+    virtual void close();  //
+    virtual QHiMDTrack himdTrack(unsigned int trkindex);  //
+    QString dumpmp3(const QHiMDTrack &trk, QString file);
+    QString dumpoma(const QHiMDTrack & track, QString file);
+    QString dumppcm(const QHiMDTrack &track, QString file);
+    virtual void upload(unsigned int trackidx, QString path);
+    virtual void batchUpload(QMDTrackIndexList tlist, QString path);
+
+};
+
+#endif // QMDDEVICE_H
diff --git a/qhimdtransfer/qmdmodel.cpp b/qhimdtransfer/qmdmodel.cpp
new file mode 100644
index 0000000..abbdddc
--- /dev/null
+++ b/qhimdtransfer/qmdmodel.cpp
@@ -0,0 +1,358 @@
+#include <QtGui/QFont>
+#include <QtGui/QFontMetrics>
+#include <qmdmodel.h>
+
+
+enum hcolumnum {
+  ColId, ColTitle, ColArtist, ColAlbum, ColLength, ColCodec, ColUploadable,
+  LAST_hcolumnnum = ColUploadable
+};
+
+enum ncolumnum {
+  CoId, CoGroup, CoTitle, CoLength, CoCodec, CoUploadable,
+  LAST_ncolumnnum = CoUploadable
+};
+
+
+/* netmd tracks model */
+QVariant QNetMDTracksModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if(orientation != Qt::Horizontal)
+        return QVariant();
+
+    if(role == Qt::SizeHintRole)
+    {
+        static QFont f;
+        static QFontMetrics met(f);
+        switch((ncolumnum)section)
+        {
+            case CoId:
+                return QSize(met.width("9999")+5, 0);
+            case CoGroup:
+            case CoTitle:
+            case CoLength:
+                return QSize(met.width("9:99:99"), 0);
+            case CoCodec:
+            case CoUploadable:
+                /* Really use the header for the metric in these columns,
+                   contents will be shorter */
+                return QAbstractListModel::headerData(section,orientation,role);
+        }
+    }
+
+    if(role == Qt::DisplayRole)
+    {
+        switch((ncolumnum)section)
+        {
+            case CoId:
+                return tr("Nr.");
+            case CoGroup:
+                return tr("Group");
+            case CoTitle:
+                return tr("Title");
+            case CoLength:
+                return tr("Length");
+            case CoCodec:
+                return tr("Format");
+            case CoUploadable:
+                return tr("Uploadable");
+        }
+    }
+    return QVariant();
+}
+
+QVariant QNetMDTracksModel::data(const QModelIndex & index, int role) const
+{
+    if(role == Qt::TextAlignmentRole &&
+       (index.column() == CoId || index.column() == CoLength))
+        return Qt::AlignRight;
+
+    if(index.row() >= rowCount())
+        return QVariant();
+
+    QNetMDTrack track = allTracks[index.row()];
+
+    if(role == Qt::CheckStateRole && index.column() == CoUploadable)
+        return track.copyprotected() ? Qt::Unchecked : Qt::Checked;
+
+    if(role == Qt::DisplayRole)
+    {
+        switch((ncolumnum)index.column())
+        {
+            case CoId:
+                return index.row() + 1;
+            case CoGroup:
+                return track.group();
+            case CoTitle:
+                return track.title();
+            case CoLength:
+            {
+                QTime t = track.duration();
+                if(t < QTime(1,0,0))
+                    return t.toString("m:ss");
+                else
+                    return t.toString("h:mm:ss");
+            }
+            case CoCodec:
+                return track.codecname();
+            case CoUploadable:
+                return QVariant(); /* Displayed by checkbox */
+        }
+    }
+    return QVariant();
+}
+
+int QNetMDTracksModel::rowCount(const QModelIndex &) const
+{
+    if(ndev == NULL)
+        return 0;
+
+    return ndev->trackCount();
+}
+
+int QNetMDTracksModel::columnCount(const QModelIndex &) const
+{
+    return LAST_ncolumnnum+1;
+}
+
+QString QNetMDTracksModel::open(QMDDevice * device)
+{
+    int i = 0;
+    QString ret = "error opening net device";
+
+    if(ndev != NULL)
+        close();
+
+    if(device->deviceType() == NETMD_DEVICE)
+    {
+        ndev = static_cast<QNetMDDevice *>(device);
+        ret = ndev->open();
+    }
+
+    if(!ret.isEmpty())
+        close();
+
+    /* fetch track info for all tracks first, getting track info inside data() function is very slow */
+    for(; i < ndev->trackCount(); i++)
+        allTracks.append(ndev->netmdTrack(i));
+
+    reset();	/* inform views that the model contents changed */
+    return ret;
+}
+
+bool QNetMDTracksModel::is_open()
+{
+    return ndev->isOpen();
+}
+
+void QNetMDTracksModel::close()
+{
+    if(ndev != NULL && ndev->isOpen())
+        ndev->close();
+
+    ndev = NULL;
+
+    allTracks.clear();
+    reset();	/* inform views that the model contents changed */
+}
+
+QNetMDTrack QNetMDTracksModel::track(int trkidx) const
+{
+    return ndev->netmdTrack(trkidx);
+}
+
+QNetMDTrackList QNetMDTracksModel::tracks(const QModelIndexList & modelindices) const
+{
+    QNetMDTrackList tracks;
+    QModelIndex index;
+
+    foreach(index, modelindices)
+        tracks.append(ndev->netmdTrack(index.row()));
+    return tracks;
+}
+
+QStringList QNetMDTracksModel::downloadableFileExtensions() const
+{
+        return ndev->downloadableFileExtensions();
+}
+
+
+/* himd tracks model */
+
+QVariant QHiMDTracksModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if(orientation != Qt::Horizontal)
+        return QVariant();
+
+    if(role == Qt::SizeHintRole)
+    {
+        static QFont f;
+        static QFontMetrics met(f);
+        switch((hcolumnum)section)
+        {
+            case ColId:
+                return QSize(met.width("9999")+5, 0);
+            case ColTitle:
+            case ColArtist:
+            case ColAlbum:
+                return QSize(25*met.averageCharWidth(), 0);
+            case ColLength:
+                return QSize(met.width("9:99:99"), 0);
+            case ColCodec:
+            case ColUploadable:
+                /* Really use the header for the metric in these columns,
+                   contents will be shorter */
+                return QAbstractListModel::headerData(section,orientation,role);
+        }
+    }
+
+    if(role == Qt::DisplayRole)
+    {
+        switch((hcolumnum)section)
+        {
+            case ColId:
+                return tr("Nr.");
+            case ColTitle:
+                return tr("Title");
+            case ColArtist:
+                return tr("Artist");
+            case ColAlbum:
+                return tr("Album");
+            case ColLength:
+                return tr("Length");
+            case ColCodec:
+                return tr("Format");
+            case ColUploadable:
+                return tr("Uploadable");
+        }
+    }
+    return QVariant();
+}
+
+QVariant QHiMDTracksModel::data(const QModelIndex & index, int role) const
+{
+    if(role == Qt::TextAlignmentRole &&
+       (index.column() == ColId || index.column() == ColLength))
+        return Qt::AlignRight;
+
+    if(index.row() >= rowCount())
+        return QVariant();
+
+    QHiMDTrack track = hdev->himdTrack(index.row());
+
+    if(role == Qt::CheckStateRole && index.column() == ColUploadable)
+        return track.copyprotected() ? Qt::Unchecked : Qt::Checked;
+
+    if(role == Qt::DisplayRole)
+    {
+        switch((hcolumnum)index.column())
+        {
+            case ColId:
+                return index.row() + 1;
+            case ColTitle:
+                return track.title();
+            case ColArtist:
+                return track.artist();
+            case ColAlbum:
+                return track.album();
+            case ColLength:
+            {
+                QTime t = track.duration();
+                if(t < QTime(1,0,0))
+                    return t.toString("m:ss");
+                else
+                    return t.toString("h:mm:ss");
+            }
+            case ColCodec:
+                return track.codecname();
+            case ColUploadable:
+                return QVariant(); /* Displayed by checkbox */
+        }
+    }
+    return QVariant();
+}
+
+int QHiMDTracksModel::rowCount(const QModelIndex &) const
+{
+    if(hdev == NULL)
+        return 0;
+
+    return hdev->trackCount();
+}
+
+int QHiMDTracksModel::columnCount(const QModelIndex &) const
+{
+    return LAST_hcolumnnum+1;
+}
+
+QString QHiMDTracksModel::open(QMDDevice * device)
+{
+    QString ret = "error opening himd device";
+
+    if(hdev != NULL)
+        close();
+
+    if(device->deviceType() == HIMD_DEVICE)
+    {
+        hdev = static_cast<QHiMDDevice *>(device);
+        ret = hdev->open();
+    }
+
+    if(!ret.isEmpty())
+        close();
+
+    reset();	/* inform views that the model contents changed */
+    return ret;
+}
+
+bool QHiMDTracksModel::is_open()
+{
+    return hdev->isOpen();
+}
+
+void QHiMDTracksModel::close()
+{
+    if(hdev != NULL && hdev->isOpen())
+        hdev->close();
+
+    hdev = NULL;
+
+    reset();	/* inform views that the model contents changed */
+}
+
+QHiMDTrack QHiMDTracksModel::track(int trknum) const
+{
+    return hdev->himdTrack(trknum);
+}
+
+QHiMDTrackList QHiMDTracksModel::tracks(const QModelIndexList & modelindices) const
+{
+    QHiMDTrackList tracks;
+    QModelIndex index;
+
+    foreach(index, modelindices)
+        tracks.append(hdev->himdTrack(index.row()));
+    return tracks;
+}
+
+QStringList QHiMDTracksModel::downloadableFileExtensions() const
+{
+        return hdev->downloadableFileExtensions();
+}
+
+
+/* QFileSystemModel stuff */
+
+Qt::ItemFlags QHiMDFileSystemModel::flags(const QModelIndex &index) const
+{
+    if(!isDir(index) && !selectableExtensions.contains((fileInfo(index).suffix()), Qt::CaseInsensitive))
+        return Qt::NoItemFlags;   //not selectable, not enabled (grayed out in the browser)
+
+    return QFileSystemModel::flags(index);
+}
+
+void QHiMDFileSystemModel::setSelectableExtensions(QStringList extensions)
+{
+    selectableExtensions = extensions;
+    reset();
+}
diff --git a/qhimdtransfer/qmdmodel.h b/qhimdtransfer/qmdmodel.h
new file mode 100644
index 0000000..9dd644e
--- /dev/null
+++ b/qhimdtransfer/qmdmodel.h
@@ -0,0 +1,80 @@
+#ifndef QHIMDMODEL_H
+#define QHIMDMODEL_H
+
+#include <QtCore/QAbstractListModel>
+
+#include <QtCore/QList>
+#include <QtCore/QStringList>
+#include <QtGui/QFileSystemModel>
+#include <qmdtrack.h>
+#include <qmddevice.h>
+
+class QMDTracksModel : public QAbstractListModel {
+    Q_OBJECT
+
+    QMDDevice * dev;
+public:
+    QMDTracksModel() : dev(NULL) {}
+    /* QAbstractListModel stuff */
+    virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const {return QVariant();}
+    virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const {return QVariant();}
+    virtual int rowCount(const QModelIndex & parent = QModelIndex() ) const {return 0;}
+    virtual int columnCount(const QModelIndex & parent = QModelIndex() ) const {return 0;}
+    /* dummy data for unknown devices */
+    virtual QString open(QMDDevice *device = NULL) {return tr("no known device type specified");}
+    virtual bool is_open() {return false;}
+    virtual void close() {}
+    QStringList downloadableFileExtensions() const {return QStringList();}
+};
+
+class QNetMDTracksModel : public QMDTracksModel {
+    Q_OBJECT
+
+    QNetMDDevice * ndev;
+    QNetMDTrackList allTracks;
+public:
+    QNetMDTracksModel() {ndev = NULL;}
+    /* QAbstractListModel stuff */
+    virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+    virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
+    virtual int rowCount(const QModelIndex & parent = QModelIndex() ) const;
+    virtual int columnCount(const QModelIndex & parent = QModelIndex() ) const;
+    /* NetMD device stuff */
+    QString open(QMDDevice *device);	/* returns null if OK, error message otherwise */
+    virtual bool is_open();
+    void close();
+    QNetMDTrack track(int trkidx) const;
+    virtual QNetMDTrackList tracks(const QModelIndexList & indices) const;  // should be QMDTrackList later
+    QStringList downloadableFileExtensions() const;
+};
+
+class QHiMDTracksModel : public QMDTracksModel {
+    Q_OBJECT
+
+    QHiMDDevice * hdev;
+public:
+    QHiMDTracksModel() {hdev = NULL;}
+    /* QAbstractListModel stuff */
+    virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+    virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
+    virtual int rowCount(const QModelIndex & parent = QModelIndex() ) const;
+    virtual int columnCount(const QModelIndex & parent = QModelIndex() ) const;
+    /* HiMD containter stuff */
+    virtual QString open(QMDDevice *device);	/* returns null if OK, error message otherwise */
+    virtual bool is_open();
+    virtual void close();
+    virtual QHiMDTrack track(int trackidx) const;
+    virtual QHiMDTrackList tracks(const QModelIndexList & indices) const;  // should be QMDTrackList later
+    QStringList downloadableFileExtensions() const;
+};
+
+class QHiMDFileSystemModel : public QFileSystemModel {
+    Q_OBJECT
+
+    QStringList selectableExtensions;
+public:
+    virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+    void setSelectableExtensions(QStringList extensions);
+};
+
+#endif
diff --git a/qhimdtransfer/qmdtrack.cpp b/qhimdtransfer/qmdtrack.cpp
new file mode 100644
index 0000000..84643f8
--- /dev/null
+++ b/qhimdtransfer/qmdtrack.cpp
@@ -0,0 +1,204 @@
+#include "qmdtrack.h"
+
+static QString get_himd_str(struct himd * himd, int idx)
+{
+    QString outstr;
+    char * str;
+    if(!idx)
+        return QString();
+    str = himd_get_string_utf8(himd, idx, NULL, NULL);
+    if(!str)
+        return QString();
+
+    outstr = QString::fromUtf8(str);
+    himd_free(str);
+    return outstr;
+}
+
+QHiMDTrack::QHiMDTrack(struct himd * himd, unsigned int trackindex) : himd(himd), trknum(trackindex)
+{
+    trackslot = himd_get_trackslot(himd, trackindex, NULL);
+    if(trackslot != 0)
+        if(himd_get_track_info(himd, trackslot, &ti, NULL) < 0)
+            trackslot = -1;
+}
+
+unsigned int QHiMDTrack::tracknum() const
+{
+    return trknum;
+}
+
+QString QHiMDTrack::title() const
+{
+    if(trackslot != 0)
+        return get_himd_str(himd, ti.title);
+    else
+        return QString();
+}
+
+QString QHiMDTrack::artist() const
+{
+    if(trackslot != 0)
+        return get_himd_str(himd, ti.artist);
+    else
+        return QString();
+}
+
+QString QHiMDTrack::album() const
+{
+    if(trackslot != 0)
+        return get_himd_str(himd, ti.album);
+    else
+        return QString();
+}
+
+QString QHiMDTrack::codecname() const
+{
+    if(trackslot != 0)
+        return himd_get_codec_name(&ti);
+    else
+        return QString();
+}
+
+QTime QHiMDTrack::duration() const
+{
+    QTime t;
+    if(trackslot != 0)
+        return t.addSecs(ti.seconds);
+    else
+        return t;
+}
+
+bool QHiMDTrack::copyprotected() const
+{
+    if(trackslot != 0)
+        return !himd_track_uploadable(himd, &ti);
+    return true;
+}
+
+int QHiMDTrack::blockcount() const
+{
+    if(trackslot != 0)
+        return himd_track_blocks(himd, &ti, NULL);
+    else
+        return 0;
+}
+
+QString QHiMDTrack::openMpegStream(struct himd_mp3stream * str) const
+{
+    struct himderrinfo status;
+    if(himd_mp3stream_open(himd, trackslot, str, &status) < 0)
+        return QString::fromUtf8(status.statusmsg);
+    return QString();
+}
+
+QString QHiMDTrack::openNonMpegStream(struct himd_nonmp3stream * str) const
+{
+    struct himderrinfo status;
+    if(himd_nonmp3stream_open(himd, trackslot, str, &status) < 0)
+        return QString::fromUtf8(status.statusmsg);
+    return QString();
+}
+
+QByteArray QHiMDTrack::makeEA3Header() const
+{
+    char header[EA3_FORMAT_HEADER_SIZE];
+    make_ea3_format_header(header, &ti.codec_info);
+    return QByteArray(header,EA3_FORMAT_HEADER_SIZE);
+}
+
+
+QNetMDTrack::QNetMDTrack(netmd_dev_handle * deviceh, minidisc * my_md, unsigned int trackindex)
+{
+    uint8_t g;
+    struct netmd_pair const *bitrate;
+    unsigned char bitrate_id;
+    unsigned char channel;
+    char *name, buffer[256];
+
+    devh = deviceh;
+    md = my_md;
+    trkindex = trackindex;
+
+    if(netmd_request_title(devh, trkindex, buffer, sizeof(buffer)) < 0)
+    {
+        trkindex = -1;
+        return;  // no track with this trackindex
+    }
+
+    /* Figure out which group this track is in */
+    for( g = 1; g < md->group_count; g++ )
+    {
+        if( (md->groups[g].start <= trkindex+1U) && (md->groups[g].finish >= trkindex+1U ))
+        {
+            groupstring = QString(md->groups[g].name);
+            break;
+        }
+    }
+
+    netmd_request_track_time(devh, trkindex, &time);
+    netmd_request_track_flags(devh, trkindex, &flags);
+    netmd_request_track_bitrate(devh, trkindex, &bitrate_id, &channel);
+
+    bitrate = find_pair(bitrate_id, bitrates);
+
+    /* Skip 'LP:' prefix... the codec type shows up in the list anyway*/
+    name = strncmp( buffer, "LP:", 3 ) ? buffer : buffer+3 ;
+
+    titlestring = QString(name);
+    codecstring = QString(bitrate->name);
+}
+
+unsigned int QNetMDTrack::tracknum() const
+{
+    return (trkindex < 0 ? trkindex : trkindex + 1);
+}
+
+QString QNetMDTrack::group() const
+{
+    if(trkindex < 0)
+        return QString();
+
+    return groupstring;
+}
+
+QString QNetMDTrack::title() const
+{
+    if(trkindex < 0)
+        return QString();
+
+    return titlestring;
+}
+
+QString QNetMDTrack::codecname() const
+{
+    if(trkindex < 0)
+        return QString();
+
+    return codecstring;
+}
+
+QTime QNetMDTrack::duration() const
+{
+    QTime t;
+
+    if(trkindex < 0)
+        return t;
+
+    return t.addSecs( time.minute * 60 + time.second);
+}
+
+bool QNetMDTrack::copyprotected() const
+{
+    switch(flags)
+    {
+    case 0x00 : return false;
+    case 0x03 : return true;
+    default : return true;   // return true if unknown
+    }
+}
+
+int QNetMDTrack::blockcount() const
+{
+    return 0;  // not implemented, yet
+}
diff --git a/qhimdtransfer/qmdtrack.h b/qhimdtransfer/qmdtrack.h
new file mode 100644
index 0000000..c7f30ad
--- /dev/null
+++ b/qhimdtransfer/qmdtrack.h
@@ -0,0 +1,81 @@
+#ifndef QMDTRACK_H
+#define QMDTRACK_H
+
+#include <QtCore/QTime>
+#include "himd.h"
+#include "sony_oma.h"
+
+#ifdef Q_OS_WIN
+    #ifdef WINVER            // WINVER needs to be 0x500 or later to make the windows autodetection mechanism work and it
+        #undef WINVER        // must be defined correctly before including libusb.h (included from libnetmd.h), else it will be defined
+    #endif                   // in windef.h to 0x400
+    #define WINVER 0x500
+#endif
+
+extern "C" {
+#include <libnetmd.h>
+}
+
+class QMDTrack
+{
+public:
+    QMDTrack() {}   // returns dummy data, implemented to have a common class name with common members
+    virtual unsigned int tracknum() const {return -1;}
+    virtual QString group() const {return QString();}
+    virtual QString title() const {return QString();}
+    virtual QString artist() const {return QString();}
+    virtual QString album() const {return QString();}
+    virtual QString codecname() const {return QString();}
+    virtual QTime duration() const {return QTime();}
+    virtual bool copyprotected() const {return true;}
+    virtual int blockcount() const {return 0;}
+};
+
+class QHiMDTrack : public QMDTrack{
+    struct himd * himd;
+    unsigned int trknum;
+    unsigned int trackslot;
+    struct trackinfo ti;
+public:
+    QHiMDTrack(struct himd * himd, unsigned int trackindex);
+    virtual unsigned int tracknum() const;
+    virtual QString title() const;
+    virtual QString artist() const;
+    virtual QString album() const;
+    virtual QString codecname() const;
+    virtual QTime duration() const;
+    virtual bool copyprotected() const;
+    virtual int blockcount() const;
+
+    QString openMpegStream(struct himd_mp3stream * str) const;
+    QString openNonMpegStream(struct himd_nonmp3stream * str) const;
+    QByteArray makeEA3Header() const;
+};
+
+class QNetMDTrack : public QMDTrack {
+    netmd_dev_handle * devh;
+    minidisc * md;
+    uint8_t trkindex;
+    struct netmd_track time;
+    unsigned char flags;
+    QString groupstring;
+    QString titlestring;
+    QString codecstring;
+
+public:
+    QNetMDTrack(netmd_dev_handle *deviceh, minidisc * my_md, unsigned int trackindex);
+    virtual unsigned int tracknum() const;
+    virtual QString group() const;
+    virtual QString title() const;
+    virtual QString codecname() const;
+    virtual QTime duration() const;
+    virtual bool copyprotected() const;
+    virtual int blockcount() const;
+};
+
+typedef QList<QMDTrack> QMDTrackList;
+typedef QList<QHiMDTrack> QHiMDTrackList;
+typedef QList<QNetMDTrack> QNetMDTrackList;
+typedef QList<unsigned int> QMDTrackIndexList;
+
+#endif // QMDTRACK_H
-- 
1.8.0.msysgit.0

<-- thread -->
<-- date -->
  • Follow-Ups:
    • Re: [linux-minidisc] implementing netmd support in the gui application
      • From: Thomas Arp <manner.moe@gmx.de>
  • References:
    • Re: [linux-minidisc] implementing netmd support in the gui application
      • From: Thomas Arp <manner.moe@gmx.de>
  • linux-minidisc - January 2013 - Archives indexes sorted by:
    [ thread ] [ subject ] [ author ] [ date ]
  • Complete archive of the linux-minidisc mailing list
  • More info on this list...

Hilfe

  • FAQ
  • Dienstbeschreibung
  • ZEDAT Beratung
  • postmaster@lists.fu-berlin.de

Service-Navigation

  • Startseite
  • Listenauswahl

Einrichtung Mailingliste

  • ZEDAT-Portal
  • Mailinglisten Portal