Beispiel #1
0
QVariant PodcastParser::Load(QIODevice* device, const QUrl& url) const {
  QXmlStreamReader reader(device);

  while (!reader.atEnd()) {
    switch (reader.readNext()) {
    case QXmlStreamReader::StartElement: {
      const QStringRef name = reader.name();
      if (name == "rss") {
        Podcast podcast;
        if (!ParseRss(&reader, &podcast)) {
          return QVariant();
        } else {
          podcast.set_url(url);
          return QVariant::fromValue(podcast);
        }
      } else if (name == "opml") {
        OpmlContainer container;
        if (!ParseOpml(&reader, &container)) {
          return QVariant();
        } else {
          container.url = url;
          return QVariant::fromValue(container);
        }
      }

      return QVariant();
    }

    default:
      break;
    }
  }

  return QVariant();
}
Beispiel #2
0
void PodcastService::CopyToDevice(const QModelIndexList& episode_indexes,
                                  const QModelIndexList& podcast_indexes) {
  PodcastEpisode episode_tmp;
  SongList songs;
  PodcastEpisodeList episodes;
  Podcast podcast;
  for (const QModelIndex& index : episode_indexes) {
    episode_tmp = index.data(Role_Episode).value<PodcastEpisode>();
    if (episode_tmp.downloaded()) episodes << episode_tmp;
  }

  for (const QModelIndex& podcast : podcast_indexes) {
    for (int i = 0; i < podcast.model()->rowCount(podcast); ++i) {
      const QModelIndex& index = podcast.child(i, 0);
      episode_tmp = index.data(Role_Episode).value<PodcastEpisode>();
      if (episode_tmp.downloaded() && !episode_tmp.listened())
        episodes << episode_tmp;
    }
  }
  for (const PodcastEpisode& episode : episodes) {
    podcast = backend_->GetSubscriptionById(episode.podcast_database_id());
    songs.append(episode.ToSong(podcast));
  }

  organise_dialog_->SetDestinationModel(
      app_->device_manager()->connected_devices_model(), true);
  organise_dialog_->SetCopy(true);
  if (organise_dialog_->SetSongs(songs)) organise_dialog_->show();
}
Beispiel #3
0
void PodcastBackend::Unsubscribe(const Podcast& podcast) {
  // If this podcast is not already in the database, do nothing
  if (!podcast.is_valid()) {
    return;
  }

  QMutexLocker l(db_->Mutex());
  QSqlDatabase db(db_->Connect());
  ScopedTransaction t(&db);

  // Remove the podcast.
  QSqlQuery q("DELETE FROM podcasts WHERE ROWID = :id", db);
  q.bindValue(":id", podcast.database_id());
  q.exec();
  if (db_->CheckErrors(q)) return;

  // Remove all episodes in the podcast
  q = QSqlQuery("DELETE FROM podcast_episodes WHERE podcast_id = :id", db);
  q.bindValue(":id", podcast.database_id());
  q.exec();
  if (db_->CheckErrors(q)) return;

  t.Commit();

  emit SubscriptionRemoved(podcast);
}
Beispiel #4
0
Song PodcastEpisode::ToSong(const Podcast& podcast) const {
  Song ret;
  ret.set_valid(true);
  ret.set_title(title().simplified());
  ret.set_artist(author().simplified());
  ret.set_length_nanosec(kNsecPerSec * duration_secs());
  ret.set_year(publication_date().date().year());
  ret.set_comment(description());

  if (downloaded() && QFile::exists(local_url().toLocalFile())) {
    ret.set_url(local_url());
  } else {
    ret.set_url(url());
  }

  ret.set_basefilename(QFileInfo(ret.url().path()).fileName());

  // Use information from the podcast if it's set
  if (podcast.is_valid()) {
    ret.set_album(podcast.title().simplified());
    ret.set_art_automatic(podcast.ImageUrlLarge().toString());
  }

  return ret;
}
void PodcastInfoWidget::SetPodcast(const Podcast& podcast) {
  if (image_id_) {
    app_->album_cover_loader()->CancelTask(image_id_);
    image_id_ = 0;
  }

  podcast_ = podcast;

  if (podcast.ImageUrlLarge().isValid()) {
    // Start loading an image for this item.
    image_id_ = app_->album_cover_loader()->LoadImageAsync(
          cover_options_, podcast.ImageUrlLarge().toString(), QString());
  }

  ui_->image->hide();

  SetText(podcast.title(), ui_->title);
  SetText(podcast.description(), ui_->description);
  SetText(podcast.copyright(), ui_->copyright, ui_->copyright_label);
  SetText(podcast.author(), ui_->author, ui_->author_label);
  SetText(podcast.owner_name(), ui_->owner, ui_->owner_label);
  SetText(podcast.link().toString(), ui_->website, ui_->website_label);
  SetText(podcast.extra("gpodder:subscribers").toString(), ui_->subscribers, ui_->subscribers_label);

  if (!image_id_) {
    emit LoadingFinished();
  }
}
Beispiel #6
0
void GPodderSearchPage::SearchFinished(mygpo::PodcastListPtr list) {
  emit Busy(false);

  model()->clear();

  for (mygpo::PodcastPtr gpo_podcast : list->list()) {
    Podcast podcast;
    podcast.InitFromGpo(gpo_podcast.data());

    model()->appendRow(model()->CreatePodcastItem(podcast));
  }
}
Beispiel #7
0
void PodcastParser::ParseOutline(QXmlStreamReader* reader, OpmlContainer* ret) const {
  while (!reader->atEnd()) {
    QXmlStreamReader::TokenType type = reader->readNext();
    switch (type) {
    case QXmlStreamReader::StartElement: {
      const QStringRef name = reader->name();
      if (name != "outline") {
        Utilities::ConsumeCurrentElement(reader);
        continue;
      }

      QXmlStreamAttributes attributes = reader->attributes();

      if (attributes.value("type").toString() == "rss") {
        // Parse the feed and add it to this container
        Podcast podcast;
        podcast.set_description(attributes.value("description").toString());
        podcast.set_title(attributes.value("text").toString());
        podcast.set_image_url_large(QUrl::fromEncoded(attributes.value("imageHref").toString().toAscii()));
        podcast.set_url(QUrl::fromEncoded(attributes.value("xmlUrl").toString().toAscii()));
        ret->feeds.append(podcast);

        // Consume any children and the EndElement.
        Utilities::ConsumeCurrentElement(reader);
      } else {
        // Create a new child container
        OpmlContainer child;

        // Take the name from the fullname attribute first if it exists.
        child.name = attributes.value("fullname").toString();
        if (child.name.isEmpty()) {
          child.name = attributes.value("text").toString();
        }

        // Parse its contents and add it to this container
        ParseOutline(reader, &child);
        ret->containers.append(child);
      }

      break;
    }

    case QXmlStreamReader::EndElement:
      return;

    default:
      break;
    }
  }
}
Beispiel #8
0
void PodcastUpdater::PodcastLoaded(PodcastUrlLoaderReply* reply, const Podcast& podcast,
                                   bool one_of_many) {
  reply->deleteLater();

  if (one_of_many) {
    if (--pending_replies_ == 0) {
      // This was the last reply we were waiting for.  Save this time as being
      // the last sucessful update and restart the timer.
      last_full_update_ = QDateTime::currentDateTime();
      SaveSettings();
      RestartTimer();
    }
  }

  if (!reply->is_success()) {
    qLog(Warning) << "Error fetching podcast at" << podcast.url() << ":"
                  << reply->error_text();
    return;
  }

  if (reply->result_type() != PodcastUrlLoaderReply::Type_Podcast) {
    qLog(Warning) << "The URL" << podcast.url() << "no longer contains a podcast";
    return;
  }

  // Get the episode URLs we had for this podcast already.
  QSet<QUrl> existing_urls;
  foreach (const PodcastEpisode& episode,
           app_->podcast_backend()->GetEpisodes(podcast.database_id())) {
    existing_urls.insert(episode.url());
  }

  // Add any new episodes
  PodcastEpisodeList new_episodes;
  foreach (const Podcast& reply_podcast, reply->podcast_results()) {
    foreach (const PodcastEpisode& episode, reply_podcast.episodes()) {
      if (!existing_urls.contains(episode.url())) {
        PodcastEpisode episode_copy(episode);
        episode_copy.set_podcast_database_id(podcast.database_id());
        new_episodes.append(episode_copy);
      }
    }
  }

  app_->podcast_backend()->AddEpisodes(&new_episodes);
  qLog(Info) << "Added" << new_episodes.count() << "new episodes for" << podcast.url();
}
Beispiel #9
0
void PodcastUpdater::SubscriptionAdded(const Podcast& podcast) {
  // Only update a new podcast immediately if it doesn't have an episode list.
  // We assume that the episode list has already been fetched recently
  // otherwise.
  if (podcast.episodes().isEmpty()) {
    UpdatePodcastNow(podcast);
  }
}
Beispiel #10
0
Podcast PodcastBackend::GetSubscriptionByUrl(const QUrl& url) {
  Podcast ret;

  QMutexLocker l(db_->Mutex());
  QSqlDatabase db(db_->Connect());

  QSqlQuery q("SELECT ROWID, " + Podcast::kColumnSpec +
                  " FROM podcasts"
                  " WHERE url = :url",
              db);
  q.bindValue(":url", url.toEncoded());
  q.exec();
  if (!db_->CheckErrors(q) && q.next()) {
    ret.InitFromQuery(q);
  }

  return ret;
}
Beispiel #11
0
Podcast PodcastBackend::GetSubscriptionById(int id) {
  Podcast ret;

  QMutexLocker l(db_->Mutex());
  QSqlDatabase db(db_->Connect());

  QSqlQuery q("SELECT ROWID, " + Podcast::kColumnSpec +
                  " FROM podcasts"
                  " WHERE ROWID = :id",
              db);
  q.bindValue(":id", id);
  q.exec();
  if (!db_->CheckErrors(q) && q.next()) {
    ret.InitFromQuery(q);
  }

  return ret;
}
Beispiel #12
0
PodcastList PodcastBackend::GetAllSubscriptions() {
  PodcastList ret;

  QMutexLocker l(db_->Mutex());
  QSqlDatabase db(db_->Connect());

  QSqlQuery q("SELECT ROWID, " + Podcast::kColumnSpec + " FROM podcasts", db);
  q.exec();
  if (db_->CheckErrors(q)) return ret;

  while (q.next()) {
    Podcast podcast;
    podcast.InitFromQuery(q);
    ret << podcast;
  }

  return ret;
}
Beispiel #13
0
void PodcastService::CancelDownload(const QModelIndexList& episode_indexes,
                                    const QModelIndexList& podcast_indexes) {
  PodcastEpisode episode_tmp;
  SongList songs;
  PodcastEpisodeList episodes;
  Podcast podcast;
  for (const QModelIndex& index : episode_indexes) {
    episode_tmp = index.data(Role_Episode).value<PodcastEpisode>();
    episodes << episode_tmp;
  }

  for (const QModelIndex& podcast : podcast_indexes) {
    for (int i = 0; i < podcast.model()->rowCount(podcast); ++i) {
      const QModelIndex& index = podcast.child(i, 0);
      episode_tmp = index.data(Role_Episode).value<PodcastEpisode>();
      episodes << episode_tmp;
    }
  }
  episodes = app_->podcast_downloader()->EpisodesDownloading(episodes);
  app_->podcast_downloader()->cancelDownload(episodes);
}
void ITunesSearchPage::SearchFinished(QNetworkReply* reply) {
  reply->deleteLater();
  emit Busy(false);

  model()->clear();

  // Was there a network error?
  if (reply->error() != QNetworkReply::NoError) {
    QMessageBox::warning(this, tr("Failed to fetch podcasts"),
                         reply->errorString());
    return;
  }

  QJson::Parser parser;
  QVariant data = parser.parse(reply);

  // Was it valid JSON?
  if (data.isNull()) {
    QMessageBox::warning(
        this, tr("Failed to fetch podcasts"),
        tr("There was a problem parsing the response from the iTunes Store"));
    return;
  }

  // Was there an error message in the JSON?
  if (data.toMap().contains("errorMessage")) {
    QMessageBox::warning(this, tr("Failed to fetch podcasts"),
                         data.toMap()["errorMessage"].toString());
    return;
  }

  for (const QVariant& result_variant : data.toMap()["results"].toList()) {
    QVariantMap result(result_variant.toMap());
    if (result["kind"].toString() != "podcast") {
      continue;
    }

    Podcast podcast;
    podcast.set_author(result["artistName"].toString());
    podcast.set_title(result["trackName"].toString());
    podcast.set_url(result["feedUrl"].toUrl());
    podcast.set_link(result["trackViewUrl"].toUrl());
    podcast.set_image_url_small(QUrl(result["artworkUrl30"].toString()));
    podcast.set_image_url_large(QUrl(result["artworkUrl100"].toString()));

    model()->appendRow(model()->CreatePodcastItem(podcast));
  }
}
Beispiel #15
0
void PodcastUpdater::UpdatePodcastNow(const Podcast& podcast) {
  PodcastUrlLoaderReply* reply = loader_->Load(podcast.url());
  NewClosure(reply, SIGNAL(Finished(bool)),
             this, SLOT(PodcastLoaded(PodcastUrlLoaderReply*,Podcast,bool)),
             reply, podcast, false);
}