/* ytcache.cpp -- cache youtube movies Copyright (C) 2013 martii License: GPL This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "ytcache.h" #include #include #include #include #include #if LIBCURL_VERSION_NUM < 0x071507 #include #endif #define URL_TIMEOUT 3600 #include "helpers.h" #include "settings.h" #include "set_threadname.h" #include #include static cYTCache *instance = NULL; cYTCache::cYTCache(void) { cancelled = false; thread = 0; } cYTCache::~cYTCache(void) { instance = NULL; } cYTCache *cYTCache::getInstance(void) { if (!instance) instance = new cYTCache(); return instance; } std::string cYTCache::getName(MI_MOVIE_INFO *mi, std::string ext) { switch (mi->source) { case MI_MOVIE_INFO::YT: return g_settings.downloadcache_dir + "/" + mi->ytid + "-" + to_string(mi->ytitag) + "." + ext; case MI_MOVIE_INFO::NK: return g_settings.downloadcache_dir + "/nk-" + mi->ytid + "." + ext; default: return g_settings.downloadcache_dir + "/error-" + mi->ytid + "." + ext;; } } bool cYTCache::getNameIfExists(std::string &fname, const std::string &id, int itag, std::string ext) { std::string f = g_settings.downloadcache_dir + "/" + id + "-" + to_string(itag) + "." + ext; if (access(f, R_OK)) return false; fname = f; return true; } bool cYTCache::useCachedCopy(MI_MOVIE_INFO *mi) { std::string file = getName(mi); if (access(file, R_OK)) return false; std::string xml = getName(mi, "xml"); if (!access(xml, R_OK)) { mi->file.Url = file; return true; } { OpenThreads::ScopedLock m_lock(mutex); if (pending.empty()) return false; MI_MOVIE_INFO m = pending.front(); if (compareMovieInfo(&m, mi)) { mi->file.Url = file; return true; } } return false; } int cYTCache::curlProgress(void *clientp, double dltotal, double dlnow, double /*ultotal*/, double /*ulnow*/) { cYTCache *caller = (cYTCache *) clientp; caller->dltotal = dltotal; caller->dlnow = dlnow; if (caller->cancelled) return 1; return 0; } bool cYTCache::download(MI_MOVIE_INFO *mi) { std::string file = getName(mi); std::string xml = getName(mi, "xml"); if (!access(file, R_OK) && !access(xml, R_OK)) { fprintf(stderr, "%s: %s already present and valid\n", __func__, file.c_str()); return true; } FILE * fp = fopen(file.c_str(), "wb"); if (!fp) { perror(file.c_str()); return false; } CURL *curl = curl_easy_init(); if (!curl) { fclose(fp); return false; } curl_easy_setopt(curl, CURLOPT_URL, mi->file.Url.c_str()); curl_easy_setopt(curl, CURLOPT_FILE, fp); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_TIMEOUT, URL_TIMEOUT); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, (long)1); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, cYTCache::curlProgress); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, (long)0); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); char cerror[CURL_ERROR_SIZE] = {0}; curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, cerror); if(!g_settings.softupdate_proxyserver.empty()) { curl_easy_setopt(curl, CURLOPT_PROXY, g_settings.softupdate_proxyserver.c_str()); if(!g_settings.softupdate_proxyusername.empty()) { std::string tmp = g_settings.softupdate_proxyusername + ":" + g_settings.softupdate_proxypassword; curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, tmp.c_str()); } } dltotal = 0; dlnow = 0; dlstart = time(NULL); fprintf (stderr, "downloading %s to %s\n", mi->file.Url.c_str(), file.c_str()); CURLcode res = curl_easy_perform(curl); curl_easy_cleanup(curl); fclose(fp); if (res) { fprintf (stderr, "downloading %s to %s failed: %s\n", mi->file.Url.c_str(), file.c_str(), cerror); unlink(file.c_str()); return false; } CMovieInfo cMovieInfo; CFile File; File.Name = xml; cMovieInfo.saveMovieInfo(*mi, &File); std::string thumbnail_dst = getName(mi, "jpg"); CFileHelpers fh; fh.copyFile(mi->tfile.c_str(), thumbnail_dst.c_str(), 0644); return true; } void *cYTCache::downloadThread(void *arg) { fprintf(stderr, "%s starting\n", __func__); set_threadname("ytdownload"); cYTCache *caller = (cYTCache *)arg; //CVFD::getInstance()->ShowIcon(FP_ICON_DOWNLOAD, true); while (caller->thread) { MI_MOVIE_INFO mi; { OpenThreads::ScopedLock m_lock(caller->mutex); if (caller->pending.empty()) { caller->cancelled = false; caller->thread = 0; continue; } mi = caller->pending.front(); } bool res = caller->download(&mi); caller->cancelled = false; { OpenThreads::ScopedLock m_lock(caller->mutex); if (res) caller->completed.insert(caller->completed.begin(), mi); else caller->failed.insert(caller->failed.begin(), mi); if (caller->pending.empty()) caller->thread = 0; else caller->pending.erase(caller->pending.begin()); } } //CVFD::getInstance()->ShowIcon(FP_ICON_DOWNLOAD, false); fprintf(stderr, "%s exiting\n", __func__); pthread_exit(NULL); } bool cYTCache::addToCache(MI_MOVIE_INFO *mi) { { OpenThreads::ScopedLock m_lock(mutex); pending.push_back(*mi); } if (!thread) { if (pthread_create(&thread, NULL, downloadThread, this)) { perror("pthread_create"); return false; } pthread_detach(thread); } return true; } void cYTCache::cancel(MI_MOVIE_INFO *mi) { mutex.lock(); if (pending.empty()) return; if (compareMovieInfo(mi, &pending.front())) { cancelled = true; mutex.unlock(); while (cancelled) usleep(100000); return; } else { for (std::vector::iterator it = pending.begin(); it != pending.end(); ++it) if (compareMovieInfo(&(*it), mi)) { pending.erase(it); failed.push_back(*mi); break; } } mutex.unlock(); } void cYTCache::remove(MI_MOVIE_INFO *mi) { OpenThreads::ScopedLock m_lock(mutex); if (completed.empty()) return; for (std::vector::iterator it = completed.begin(); it != completed.end(); ++it) if (compareMovieInfo(&(*it), mi)) { completed.erase(it); unlink(getName(mi).c_str()); unlink(getName(mi, "xml").c_str()); unlink(getName(mi, "jpg").c_str()); break; } } void cYTCache::cancelAll(MI_MOVIE_INFO::miSource source) { { OpenThreads::ScopedLock m_lock(mutex); if (pending.empty()) return; if (pending.size() > 1) for (std::vector::iterator it = pending.begin() + 1; it != pending.end();){ if ((*it).source == source) { failed.push_back(*it); it = pending.erase(it); } else ++it; } if (pending.front().source != source) return; } cancelled = true; while (thread) usleep(100000); { OpenThreads::ScopedLock m_lock(mutex); cancelled = false; if (!pending.empty()) { if (pthread_create(&thread, NULL, downloadThread, this)) { perror("pthread_create"); return; } pthread_detach(thread); } } } std::vector cYTCache::getFailed(MI_MOVIE_INFO::miSource source) { OpenThreads::ScopedLock m_lock(mutex); std::vector res; for (std::vector::iterator it = failed.begin(); it != failed.end(); ++it) if ((*it).source == source) res.push_back(*it); return res; } std::vector cYTCache::getCompleted(MI_MOVIE_INFO::miSource source) { OpenThreads::ScopedLock m_lock(mutex); std::vector res; for (std::vector::iterator it = completed.begin(); it != completed.end(); ++it) if ((*it).source == source) res.push_back(*it); return res; } std::vector cYTCache::getPending(MI_MOVIE_INFO::miSource source, double *p_dltotal, double *p_dlnow, time_t *p_start) { OpenThreads::ScopedLock m_lock(mutex); std::vector res; for (std::vector::iterator it = pending.begin(); it != pending.end(); ++it) if ((*it).source == source) res.push_back(*it); if (!res.empty() && pending.front().source == source) { if (p_dltotal) *p_dltotal = dltotal; if (p_dlnow) *p_dlnow = dlnow; if (p_start) *p_start = dlstart; } else { if (p_dltotal) *p_dltotal = 0; if (p_dlnow) *p_dlnow = 0; if (p_start) *p_start = 0; } return res; } void cYTCache::clearFailed(MI_MOVIE_INFO *mi) { OpenThreads::ScopedLock m_lock(mutex); if (mi) { for (std::vector::iterator it = failed.begin(); it != failed.end(); ++it) if (compareMovieInfo(&(*it), mi)) { failed.erase(it); break; } } else failed.clear(); } void cYTCache::clearCompleted(MI_MOVIE_INFO *mi) { OpenThreads::ScopedLock m_lock(mutex); if (mi) { for (std::vector::iterator it = completed.begin(); it != completed.end(); ++it) if (compareMovieInfo(&(*it), mi)) { completed.erase(it); break; } } else completed.clear(); } void cYTCache::clearFailed(MI_MOVIE_INFO::miSource source) { OpenThreads::ScopedLock m_lock(mutex); for (std::vector::iterator it = failed.begin(); it != failed.end();) if ((*it).source == source) it = failed.erase(it); else ++it; } void cYTCache::clearCompleted(MI_MOVIE_INFO::miSource source) { OpenThreads::ScopedLock m_lock(mutex); for (std::vector::iterator it = completed.begin(); it != completed.end();) if ((*it).source == source) it = completed.erase(it); else ++it; } bool cYTCache::compareMovieInfo(MI_MOVIE_INFO *a, MI_MOVIE_INFO *b) { return a->ytid == b->ytid && a->ytitag == b->ytitag && a->source == b->source; }