bool TellicoZipExporter::exec() {
  m_cancelled = false;
  Data::CollPtr coll = collection();
  if(!coll) {
    return false;
  }

  // TODO: maybe need label?
  ProgressItem& item = ProgressManager::self()->newProgressItem(this, QString(), true);
  item.setTotalSteps(100);
  connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
  ProgressItem::Done done(this);

  TellicoXMLExporter exp(coll);
  exp.setEntries(entries());
  exp.setFields(fields());
  exp.setURL(url()); // needed in case of relative URL values
  long opt = options();
  opt |= Export::ExportUTF8; // always export to UTF-8
  opt |= Export::ExportImages; // always list the images in the xml
  opt &= ~Export::ExportProgress; // don't show progress for xml export
  exp.setOptions(opt);
  exp.setIncludeImages(false); // do not include the images themselves in XML
  QByteArray xml = exp.exportXML().toByteArray(); // encoded in utf-8
  ProgressManager::self()->setProgress(this, 5);

  QByteArray data;
  QBuffer buf(&data);

  if(m_cancelled) {
    return true; // intentionally cancelled
  }

  KZip zip(&buf);
  zip.open(QIODevice::WriteOnly);
  zip.writeFile(QLatin1String("tellico.xml"), QString(), QString(), xml, xml.size());

  if(m_includeImages) {
    ProgressManager::self()->setProgress(this, 10);
    // gonna be lazy and just increment progress every 3 images
    // it might be less, might be more
    int j = 0;
    const QString imagesDir = QLatin1String("images/");
    StringSet imageSet;
    Data::FieldList imageFields = coll->imageFields();
    // take intersection with the fields to be exported
    imageFields = QSet<Data::FieldPtr>::fromList(imageFields).intersect(fields().toSet()).toList();
    // already took 10%, only 90% left
    const int stepSize = qMax(1, (coll->entryCount() * imageFields.count()) / 90);
    foreach(Data::EntryPtr entry, entries()) {
      if(m_cancelled) {
        break;
      }
      foreach(Data::FieldPtr imageField, imageFields) {
        const QString id = entry->field(imageField);
        if(id.isEmpty() || imageSet.has(id)) {
          continue;
        }
        const Data::ImageInfo& info = ImageFactory::imageInfo(id);
        if(info.linkOnly) {
          myLog() << "not copying linked image: " << id;
          continue;
        }
        const Data::Image& img = ImageFactory::imageById(id);
        // if no image, continue
        if(img.isNull()) {
          myWarning() << "no image found for " << imageField->title() << " field";
          myWarning() << "...for the entry titled " << entry->title();
          continue;
        }
        QByteArray ba = img.byteArray();
//        myDebug() << "adding image id = " << it->field(fIt);
        zip.writeFile(imagesDir + id, QString(), QString(), ba, ba.size());
        imageSet.add(id);
        if(j%stepSize == 0) {
          ProgressManager::self()->setProgress(this, qMin(10+j/stepSize, 99));
          kapp->processEvents();
        }
        ++j;
      }
    }
  } else {