/* * request.c: Web video plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. * * $Id$ */ #include #include #include #include #include #include #include #include #include #include "request.h" #include "common.h" #include "mimetypes.h" #include "config.h" #include "timer.h" // --- cDownloadProgress --------------------------------------------------- cDownloadProgress::cDownloadProgress() { strcpy(name, "???"); downloaded = -1; total = -1; statusCode = -1; req = NULL; } void cDownloadProgress::AssociateWith(cFileDownloadRequest *request) { req = request; } void cDownloadProgress::SetContentLength(long bytes) { total = bytes; } void cDownloadProgress::SetTitle(const char *title) { cMutexLock lock(&mutex); strncpy(name, title, NAME_LEN-1); name[NAME_LEN-1] = '\0'; } void cDownloadProgress::Progress(long downloadedbytes) { // Atomic operation, no mutex needed downloaded = downloadedbytes; } void cDownloadProgress::MarkDone(int errorcode, cString pharse) { cMutexLock lock(&mutex); statusCode = errorcode; statusPharse = pharse; } bool cDownloadProgress::IsFinished() { return statusCode != -1; } cString cDownloadProgress::GetTitle() { cMutexLock lock(&mutex); if (req && req->IsAborted()) return cString::sprintf("[%s] %s", tr("Aborted"), name); else return cString(name); } cString cDownloadProgress::GetPercentage() { cMutexLock lock(&mutex); if ((const char*)statusPharse != NULL && statusCode != 0) // TRANSLATORS: at most 5 characters return cString(tr("Error")); else if ((downloaded < 0) || (total < 0)) return cString("???"); else return cString::sprintf("%3d%%", (int) (100*(float)downloaded/total + 0.5)); } cString cDownloadProgress::GetStatusPharse() { cMutexLock lock(&mutex); return statusPharse; } bool cDownloadProgress::Error() { return (const char *)statusPharse != NULL; } // --- cProgressVector ----------------------------------------------------- cDownloadProgress *cProgressVector::NewDownload() { cDownloadProgress *progress = new cDownloadProgress(); Append(progress); return progress; } // --- cMenuRequest -------------------------------------------------------- cMenuRequest::cMenuRequest(int ID, const char *wvtreference) : reqID(ID), aborted(false), finished(false), status(0), webvi(-1), handle(-1), timer(NULL) { wvtref = strdup(wvtreference); } cMenuRequest::~cMenuRequest() { if (handle != -1) { if (!finished) Abort(); webvi_delete_handle(webvi, handle); } // do not delete timer } ssize_t cMenuRequest::WriteCallback(const char *ptr, size_t len, void *request) { cMenuRequest *instance = (cMenuRequest *)request; if (instance) return instance->WriteData(ptr, len); else return len; } ssize_t cMenuRequest::WriteData(const char *ptr, size_t len) { return inBuffer.Put(ptr, len); } char *cMenuRequest::ExtractSiteName(const char *ref) { if (strncmp(ref, "wvt:///", 7) != 0) return NULL; const char *first = ref+7; const char *last = strchr(first, '/'); if (!last) last = first+strlen(first); return strndup(first, last-first); } void cMenuRequest::AppendQualityParamsToRef() { if (!wvtref) return; char *site = ExtractSiteName(wvtref); if (site) { const char *min = webvideoConfig->GetMinQuality(site, GetType()); const char *max = webvideoConfig->GetMaxQuality(site, GetType()); free(site); if (min && !max) { cString newref = cString::sprintf("%s&minquality=%s", wvtref, min); free(wvtref); wvtref = strdup((const char *)newref); } else if (!min && max) { cString newref = cString::sprintf("%s&maxquality=%s", wvtref, max); free(wvtref); wvtref = strdup((const char *)newref); } else if (min && max) { cString newref = cString::sprintf("%s&minquality=%s&maxquality=%s", wvtref, min, max); free(wvtref); wvtref = strdup((const char *)newref); } } } WebviHandle cMenuRequest::PrepareHandle() { if (handle == -1) { handle = webvi_new_request(webvi, wvtref, WEBVIREQ_MENU); if (handle != -1) { webvi_set_opt(webvi, handle, WEBVIOPT_WRITEFUNC, WriteCallback); webvi_set_opt(webvi, handle, WEBVIOPT_WRITEDATA, this); } } return handle; } bool cMenuRequest::Start(WebviCtx webvictx) { debug("starting request %d", reqID); webvi = webvictx; if ((PrepareHandle() != -1) && (webvi_start_handle(webvi, handle) == WEBVIERR_OK)) { finished = false; return true; } else return false; } void cMenuRequest::RequestDone(int errorcode, cString pharse) { debug("RequestDone %d %s", errorcode, (const char *)pharse); finished = true; status = errorcode; statusPharse = pharse; } void cMenuRequest::Abort() { if (aborted || finished || handle == -1) return; aborted = true; webvi_stop_handle(webvi, handle); }; bool cMenuRequest::Success() { return status == 0; } cString cMenuRequest::GetStatusPharse() { return statusPharse; } cString cMenuRequest::GetResponse() { size_t len = inBuffer.Length(); const char *src = inBuffer.Get(); char *buf = (char *)malloc((len+1)*sizeof(char)); strncpy(buf, src, len); buf[len] = '\0'; return cString(buf, true); } // --- cFileDownloadRequest ------------------------------------------------ cFileDownloadRequest::cFileDownloadRequest(int ID, const char *streamref, cDownloadProgress *progress) : cMenuRequest(ID, streamref), title(NULL), bytesDownloaded(0), contentLength(-1), destfile(NULL), destfilename(NULL), progressUpdater(progress), state(STATE_WEBVI) { if (progressUpdater) progressUpdater->AssociateWith(this); AppendQualityParamsToRef(); } cFileDownloadRequest::~cFileDownloadRequest() { if (destfile) { destfile->Close(); delete destfile; } if (destfilename) free(destfilename); if (title) free(title); // do not delete progressUpdater } WebviHandle cFileDownloadRequest::PrepareHandle() { if (handle == -1) { handle = webvi_new_request(webvi, wvtref, WEBVIREQ_FILE); if (handle != -1) { webvi_set_opt(webvi, handle, WEBVIOPT_WRITEFUNC, WriteCallback); webvi_set_opt(webvi, handle, WEBVIOPT_WRITEDATA, this); } } return handle; } ssize_t cFileDownloadRequest::WriteData(const char *ptr, size_t len) { if (!destfile) { if (!OpenDestFile()) return -1; } bytesDownloaded += len; if (progressUpdater) progressUpdater->Progress(bytesDownloaded); return destfile->Write(ptr, len); } bool cFileDownloadRequest::OpenDestFile() { char *contentType; char *url; char *ext; cString filename; int fd, i; if (handle == -1) { error("handle == -1 while trying to open destination file"); return false; } if (destfile) delete destfile; destfile = new cUnbufferedFile; webvi_get_info(webvi, handle, WEBVIINFO_URL, &url); webvi_get_info(webvi, handle, WEBVIINFO_STREAM_TITLE, &title); webvi_get_info(webvi, handle, WEBVIINFO_CONTENT_TYPE, &contentType); webvi_get_info(webvi, handle, WEBVIINFO_CONTENT_LENGTH, &contentLength); if (!contentType || !url) { if(contentType) free(contentType); if (url) free(url); error("no content type or url, can't infer extension"); return false; } ext = GetExtension(contentType, url); free(url); free(contentType); const char *destdir = webvideoConfig->GetDownloadPath(); char *basename = strdup(title ? title : "???"); basename = safeFilename(basename, webvideoConfig->GetUseVFATNames()); i = 1; filename = cString::sprintf("%s/%s%s", destdir, basename, ext); while (true) { debug("trying to open %s", (const char *)filename); fd = destfile->Open(filename, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE); if (fd == -1 && errno == EEXIST) filename = cString::sprintf("%s/%s-%d%s", destdir, basename, i++, ext); else break; }; free(basename); free(ext); if (fd < 0) { error("Failed to open file %s: %m", (const char *)filename); delete destfile; destfile = NULL; return false; } if (destfilename) free(destfilename); destfilename = strdup(filename); info("Saving to %s", destfilename); if (progressUpdater) { progressUpdater->SetTitle(title); progressUpdater->SetContentLength(contentLength); } return true; } char *cFileDownloadRequest::GetExtension(const char *contentType, const char *url) { // Get extension from Content-Type char *ext = NULL; char *ext2 = MimeTypes->ExtensionFromMimeType(contentType); // Workaround for buggy servers: If the server claims that the mime // type is text/plain, ignore the server and fall back to extracting // the extension from the URL. This function should be called only // for video, audio or ASX files and therefore text/plain is clearly // incorrect. if (ext2 && contentType && !strcasecmp(contentType, "text/plain")) { debug("Ignoring content type text/plain, getting extension from url."); free(ext2); ext2 = NULL; } if (ext2) { // Append dot in the start of the extension ext = (char *)malloc(strlen(ext2)+2); ext[0] = '.'; ext[1] = '\0'; strcat(ext, ext2); free(ext2); return ext; } // Get extension from URL ext = extensionFromUrl(url); if (ext) return ext; // No extension! return strdup(""); } void cFileDownloadRequest::RequestDone(int errorcode, cString pharse) { if (state == STATE_WEBVI) { if (destfile) destfile->Close(); if (errorcode == 0) StartPostProcessing(); else state = STATE_FINISHED; } else if (state == STATE_POSTPROCESS) { postProcessPipe.Close(); state = STATE_FINISHED; } if (state == STATE_FINISHED) { cMenuRequest::RequestDone(errorcode, pharse); if (progressUpdater) progressUpdater->MarkDone(errorcode, pharse); } } void cFileDownloadRequest::Abort() { if (state == STATE_POSTPROCESS) postProcessPipe.Close(); cMenuRequest::Abort(); } void cFileDownloadRequest::StartPostProcessing() { state = STATE_POSTPROCESS; const char *script = webvideoConfig->GetPostProcessCmd(); if (!script || !destfilename) { state = STATE_FINISHED; return; } info("post-processing %s", destfilename); cString cmd = cString::sprintf("%s %s", (const char *)shellEscape(script), (const char *)shellEscape(destfilename)); debug("executing %s", (const char *)cmd); if (!postProcessPipe.Open(cmd, "r")) { state = STATE_FINISHED; return; } int flags = fcntl(fileno(postProcessPipe), F_GETFL, 0); flags |= O_NONBLOCK; fcntl(fileno(postProcessPipe), F_SETFL, flags); } int cFileDownloadRequest::File() { FILE *f = postProcessPipe; if (f) return fileno(f); else return -1; } bool cFileDownloadRequest::Read() { const size_t BUF_LEN = 512; char buf[BUF_LEN]; if (!(FILE *)postProcessPipe) return false; while (true) { ssize_t nbytes = read(fileno(postProcessPipe), buf, BUF_LEN); if (nbytes < 0) { if (errno != EAGAIN && errno != EINTR) { LOG_ERROR_STR("post process pipe"); return false; } } else if (nbytes == 0) { info("post-processing of %s finished", destfilename); if (IsAborted()) RequestDone(-2, "Aborted"); else RequestDone(0, ""); return true; } else { debug("pp: %.*s", nbytes, buf); if (nbytes < (ssize_t)BUF_LEN) return true; } } return true; } // --- cStreamUrlRequest --------------------------------------------------- cStreamUrlRequest::cStreamUrlRequest(int ID, const char *ref) : cMenuRequest(ID, ref) { AppendQualityParamsToRef(); } WebviHandle cStreamUrlRequest::PrepareHandle() { if (handle == -1) { handle = webvi_new_request(webvi, wvtref, WEBVIREQ_STREAMURL); if (handle != -1) { webvi_set_opt(webvi, handle, WEBVIOPT_WRITEFUNC, WriteCallback); webvi_set_opt(webvi, handle, WEBVIOPT_WRITEDATA, this); } } return handle; } // --- cTimerRequest ------------------------------------------------------- cTimerRequest::cTimerRequest(int ID, const char *ref) : cMenuRequest(ID, ref) { } // --- cRequestVector ------------------------------------------------------ cMenuRequest *cRequestVector::FindByHandle(WebviHandle handle) { for (int i=0; iGetHandle() == handle) return At(i); return NULL; }