pax_global_header00006660000000000000000000000064120165516030014511gustar00rootroot0000000000000052 comment=159e3ed960898bcf8f48822261bf453adf1d7d7f tunesviewer-1.4.99.2/000077500000000000000000000000001201655160300143355ustar00rootroot00000000000000tunesviewer-1.4.99.2/.gitignore000066400000000000000000000001501201655160300163210ustar00rootroot00000000000000*~ *.debhelper *.html *.log *.pyc *.pyo debian/*.substvars debian/files debian/tunesviewer tests/*.html tunesviewer-1.4.99.2/.mailmap000066400000000000000000000004061201655160300157560ustar00rootroot00000000000000Luke Bryan lb1programmer Luke Bryan lb1programmer Luke Bryan programmin1 tunesviewer-1.4.99.2/README.md000066400000000000000000000053471201655160300156250ustar00rootroot00000000000000# TunesViewer TunesViewer is a small, easy to use program to access iTunes-university media and podcasts in Linux. ## Features: * Direct searching, browsing, and downloading. * Supports itunes-University login, to download students-and-staff-only media. * Reveals the standard rss-podcast-feed of the itunes-podcasts, for use in any podcast software. * Includes the option to set itself as default protocol handler, to go directly from the "loading itunes-U..." page to viewing with TunesViewer. ## Non-Features: * Automatic podcast updates, auto-downloads and transfers: this program isn't a general-purpose podcast manager, but it can add podcasts to rhythmbox/gpodder/amarok/etc. * iTunes Store: this will not let you connect to iTunes store accounts or buy anything. ## Minimum System Requirements: Tunesviewer works well even on older computers, but you *must* have `pygtk` >= 2.16 and `lxml` available. This shouldn't be a problem for recent versions of Ubuntu, Fedora etc., as these prerequisites should be taken care of automatically by package managers. ## Installation: 1. Download the install package: ([.deb installer][0] for Ubuntu/Debian based operating systems, [.rpm installer][1] for Fedora/Red Hat based operating systems). 2. Open the package and it should install. After installing, you should see TunesViewer on your Applications - Internet menu. 3. Important post-installation setup: Go to TunesViewer preferences (in the Edit menu), and select the download-folder. Choose the folder you want to download to. If you select your rhythmbox/amarok library directory they should automatically add to your library. The main [project of TunesViewer][2] is hosted on [SourceForge.net][3]. ## Building a Debian Package Building a Debian Package from the git repository, ready for installation in your system is as easy as: 1. Installing the packages `build-essential`, `debhelper`, `fakeroot`, and `python` 2. Checking out the `debian` branch of this project 3. Typing `fakeroot debian/rules clean binary` ## Experimental Debian Packages Moderately recent and ready-to-install packages for Debian-based systems (like Ubuntu, Linux mint and others) taken from this development tree are provided in [Rogério Theodoro de Brito's PPA][4]. You are welcome to install them and to report potential issues with respect to the packaging in this [project's issue tracker][5]. [0]: http://sourceforge.net/projects/tunesviewer/files/tunesviewer_1.4.deb/download [1]: http://sourceforge.net/projects/tunesviewer/files/tunesviewer-1.4.noarch.rpm/download [2]: http://sourceforge.net/projects/tunesviewer [3]: http://sourceforge.net [4]: https://launchpad.net/~rbrito/+archive/ppa/ [5]: https://github.com/rbrito/tunesviewer/issues tunesviewer-1.4.99.2/doc/000077500000000000000000000000001201655160300151025ustar00rootroot00000000000000tunesviewer-1.4.99.2/doc/changelog000066400000000000000000000032561201655160300167620ustar00rootroot00000000000000TunesViewer (1.4) * Fixed some broken upper page links, and broken ebook links in lower panel. * Minor display improvements, including zoom level save. TunesViewer (1.3) * Major rewrite of parsing code, many pages should work better now. * Only one instance of the program is allowed, this fixes re-downloading which corrupted files when a second instance was opened. * Added automatic resuming of downloads when network disconnects. Added overall percent-downloaded in the download box. Fixed browser integration on systems with GNOME 3. (including Ubuntu 11.x) TunesViewer (1.2) * Fixed crash in seeHTMLElement. * Added download recovery - If the program crashes, it will start the downloads again the next time it runs. * Headings show up correctly that previously showed up as a blue box. TunesViewer (1.1) * Added webkit-debugger (right click, inspect, in the upper panel.) * Fixed Subscribe and Download links in the upper panel. * Fixed other bugs, and moved description, links, and text to upper panel. TunesViewer (1.0.1) * Fixed display problem in upper panel, and error caused by dragging links. * Fixed go menu directory TunesViewer (1.0) * Fixed viewing and downloading for some pages, and added an option to turn off the loading-animation. TunesViewer (1.0-beta) * Improved display, gtkTextView replaced with pyWebKitGTK. * Request-html-mode changed for some pages (this should fix Stanford/Harvard/Ohio/Brown University main pages), added an option for this in Display tab. * Added %l option for download naming, the new default setting sets the output as "name - author length.type". This should fix downloading of different media with the same name and author. tunesviewer-1.4.99.2/doc/tunesviewer.1000066400000000000000000000020721201655160300175450ustar00rootroot00000000000000.TH TUNESVIEWER "1" "March 2012" "TunesViewer" "User Commands" .SH NAME TunesViewer \- program to access media and podcasts from iTunes U .SH SYNOPSIS .B tunesviewer [\fIoptions\fR] .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-s\fR SEARCH, \fB\-\-search\fR=\fISEARCH\fR Give terms to search for university media in iTunes U. .TP \fB\-p\fR SEARCH, \fB\-\-search\-podcast\fR=\fISEARCH\fR Give terms to search on podcasts in iTunes U. .TP \fB\-d\fR DOWNLOADFILE, \fB\-\-download\fR=\fIDOWNLOADFILE\fR Download HTML only, without using any GUI. .TP \fB\-v\fR, \fB\-\-verbose\fR Output debug information. .TP \fB\-V\fR, \fB\-\-version\fR Output version number and exit. .SH BUGS .PP There are probably many. The authors kindly ask to be informed about incorrect behavior that you see. .SH SEE ALSO .BR mplayer (1) , .BR rhythmbox (1) , .BR vlc (1) .SH AUTHOR TunesViewer was written by Luke Brian, and Rogério Brito with the (much appreciated and kind) help of other contributors. This manpage was written by Rog\['e]rio Brito . tunesviewer-1.4.99.2/resources/000077500000000000000000000000001201655160300163475ustar00rootroot00000000000000tunesviewer-1.4.99.2/resources/TunesViewer.desktop000066400000000000000000000012161201655160300222220ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Type=Application Exec=tunesviewer %U Icon=tunesview Terminal=false Categories=Application;Network; MimeType=x-scheme-handler/itms;x-scheme-handler/itmss;x-scheme-handler/itpc; Name=TunesViewer Name[en_US]=TunesViewer Name[pt]=TunesViewer GenericName=iTunes U browser GenericName[pt]=navegador do iTunes Comment=Access iTunesU media and podcasts. Comment[en_US.utf8]=Access iTunesU media and podcasts. Comment[en_US]=Access iTunesU media and podcasts. Comment[pt_BR.utf8]=Programa pequeno e simples de usar para acessar conteúdo do iTunes U Comment[pt_BR]=Programa pequeno e simples de usar para acessar conteúdo do iTunes U tunesviewer-1.4.99.2/resources/tunesview.svg000066400000000000000000000446531201655160300211350ustar00rootroot00000000000000 image/svg+xml tunesviewer-1.4.99.2/src/000077500000000000000000000000001201655160300151245ustar00rootroot00000000000000tunesviewer-1.4.99.2/src/Javascript.js000066400000000000000000000250301201655160300175700ustar00rootroot00000000000000/* iTunes Javascript Class, added to the displayed pages. Catches iTunes-api calls from pages, such as http://r.mzstatic.com/htmlResources/6018/dt-storefront-base.jsz Copyright (C) 2009 - 2012 Luke Bryan 2011 - 2012 Rogério Theodoro de Brito and other contributors. 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. */ /*global window */ iTunes = { // Called from the page js: getMachineID: function () { "use strict"; // FIXME: Apple's javscript tries to identify what machine we are // when we click on the "Subscribe Free" button of a given course to // create an URL. // // We should see what are some valid values and use those. console.log("TunesViewer: In function "); return ""; }, getPreferences: function() { "use strict"; return { pingEnabled: true }; }, doDialogXML: function (b, d) { "use strict"; /* // FIXME: This seems to be called for the creation of a // confirmation / license agreement dialog. // // Not yet sure how exactly this should work. var i; console.log("TunesViewer: In function , b:" + b); console.log("TunesViewer: In function , d:" + d); for (i in d) { console.log("TunesViewer: " + i + " " + d[i]); } return d.okButtonAction(); */ }, playURL: function (input) { "use strict"; // Construct the preview display: var div, anchor, video; // First, create the div element div = document.createElement("div"); div.setAttribute("class", "quick-view video movie active activity-video-dialog"); div.setAttribute("style", "width:50%; height:auto; position:fixed; left: 25%; float: top ; top:10px"); div.setAttribute("id", "previewer-container"); // Create the anchor and tie it with the div element anchor = document.createElement("a"); anchor.setAttribute("class", "close-preview"); anchor.addEventListener("click", function () { this.parentNode.parentNode.removeChild(this.parentNode); }, false); div.appendChild(anchor); // Create a video element and tie it with the div element video = document.createElement("video"); video.id = "previewPlayer"; video.setAttribute("controls", "true"); div.appendChild(video); document.body.appendChild(div); // Start the media: document.getElementById("previewPlayer").src = input.url; document.getElementById("previewPlayer").play(); return "not 0"; }, showMediaPlayer: function (url_, showtype, title) { "use strict"; playURL({url: url_}); }, openURL: function (url) { "use strict"; location.href = url; }, /** Download a file described as XML */ addProtocol: function (xml) { "use strict"; if (xml.indexOf("navbar") === -1) { console.log("TunesViewer: adding download: " + xml); location.href = "download://" + xml; } }, /** Stops the preview player */ stop: function () { "use strict"; document.getElementById("previewer-container").parentNode.removeChild(document.getElementById("previewer-container")); return true; }, doPodcastDownload: function (obj, number) { "use strict"; alert("podcastdownload"+obj+number); //var keys = obj.getElementsByTagName('key'); }, doAnonymousDownload: function (obj) { "use strict"; location.href = obj.url; // It has the url... just needs a way to tell the main // program to download it (webkit transaction?) }, getUserDSID: function () { // no user id. "use strict"; return 0; }, putURLOnPasteboard: function (a, bool) { "use strict"; location.href = "copyurl://" + encodeURI(a); }, /** What version of webkit we're using, eg 'AppleWebKit/531.2' */ webkitVersion: function () { "use strict"; return (/AppleWebKit\/([\d.]+)/).exec(navigator.userAgent)[0]; } }; /*jslint unparam: true*/ function iTSVideoPreviewWithObject(obj) { "use strict"; console.log("TunesViewer: Entering the function ."); // This was meant to figure out how to get non-working previews to play. // Unfortunately it gets called many times when you click 'i' on course icon, // freezing the application. //alert(obj); } /*jslint unparam: false*/ function fixTransparent(objects) { "use strict"; var i; console.log("TunesViewer: Entering the function ."); for (i = 0; i < objects.length; i++) { // If the heading is transparent, show it. if (window.getComputedStyle(objects[i]).color === "rgba(0, 0, 0, 0)") { objects[i].style.color = "inherit"; } // Fix odd background box on iTunesU main page if (objects[i].parentNode.getAttribute("class") === "title") { objects[i].style.background = "transparent"; } } } /** * Empty function to assign to events that we want to kill. */ function TunesViewerEmptyFunction() { "use strict"; } /** * Function to remove event listeners (onmouseover, onclick, onmousedown) * of objects that we don't want to "have life". */ function removeListeners(objects) { "use strict"; var i; console.log("TunesViewer: Entering the function ."); for (i = 0; i < objects.length; i++) { objects[i].onmouseover = TunesViewerEmptyFunction; objects[i].onclick = TunesViewerEmptyFunction; objects[i].onmousedown = TunesViewerEmptyFunction; } } /* Hooking everything when the document is shown. * * FIXME: This huge thing has to be broken down into smaller pieces with * properly named functions. */ document.onpageshow = (function () { "use strict"; var as, a, css, divs, i, j, rss, previews, clickEvent, downloadMouseDownEvent, subscribePodcastClickEvent, disabledButtonClickEvent; // Fix Download"; divs[i].addEventListener('mouseDown', downloadMouseDownEvent(getAttribute('download-url')), false); } if (divs[i].getAttribute("role") === "button" && divs[i].getAttribute("aria-label") === "Subscribe Free") { rss = ""; console.log("TunesViewer: subscribe-button"); removeListeners(divs[i].parentNode); removeListeners(divs[i].parentNode.parentNode); for (j = 0; j < divs.length; j++) { if (divs[j].getAttribute("podcast-feed-url") !== null) { rss = divs[j].getAttribute("podcast-feed-url"); console.log("TunesViewer: RSS:" + rss); } } divs[i].addEventListener('click', clickEvent(rss), false); } } // FIXME: Should we change this to be a separate function "attached" // to an object that is, finally, assigned to the onpageshow event? subscribePodcastClickEvent = function (subscribePodcastUrl) { location.href = subscribePodcastUrl; }; // FIXME: Should we change this to be a separate function "attached" // to an object that is, finally, assigned to the onpageshow event? disabledButtonClickEvent = function (episodeUrl, artistName, itemName) { location.href = "download://URL" + "artistName" + "fileExtensionzip" + "songName"; }; //Fix 100% height if (document.getElementById('search-itunes-u') !== null) { document.getElementById('search-itunes-u').style.height = 90; } if (document.getElementById('search-podcast') !== null) { document.getElementById('search-podcast').style.height = 90; } // Fix selectable text, and search form height css = document.createElement("style"); css.type = "text/css"; css.innerHTML = "* { -webkit-user-select: initial !important } div.search-form {height: 90}"; document.body.appendChild(css); console.log("TunesViewer: JS OnPageShow Ran Successfully."); }()); // end Pageshow. tunesviewer-1.4.99.2/src/Parser.py000066400000000000000000000545521201655160300167450ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ The main parser to turn iTunesU xml/html into viewable page Copyright (C) 2009 - 2012 Luke Bryan 2011 - 2012 Rogério Theodoro de Brito and other contributors. 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. """ import gc import logging import re import time from lxml import etree import lxml.html from common import * def safe(obj): """ Makes an object safe to add to string, even if it is NoneType. """ if obj: return obj else: return "" class Parser: def __init__(self, url, contentType, source): # Initialized each time... self.Redirect = "" # URL to redirect to. self.Title = "" self.HTML = "" # The description-html, top panel. self.itemId = "" # The specific item selected. self.singleItem = False self.NormalPage = False self.podcast = "" self.bgcolor = "" self.mediaItems = [] #List of items to place in Liststore. self.tabMatches = [] self.tabLinks = [] self.last_text = "" # prevent duplicates from shadow-text. self.url = url self.contentType = contentType self.source = source sttime = time.time() try: #parse as xml # Remove bad XML. See: # http://stackoverflow.com/questions/1016910/how-can-i-strip-invalid-xml-characters-from-strings-in-perl bad = "[^\x09\x0A\x0D\x20-\xD7FF\xE000-\xFFFD]" self.source = re.sub(bad, " ", self.source) # now it should be valid xml. dom = etree.fromstring(self.source.replace('xmlns="http://www.apple.com/itms/"', '')) #(this xmlns causes problems with xpath) if dom.tag.find("html") > -1 or dom.tag == "{http://www.w3.org/2005/Atom}feed": # Don't want normal pages/atom pages, those are for the web browser! raise Exception elif dom.tag == "rss": # rss files are added self.HTML += "

This is a podcast feed, click Add to Podcast manager button on the toolbar to subscribe.

" items = dom.xpath("//item") logging.debug("rss: " + str(len(items))) for item in items: title = "" author = "" linkurl = "" duration = "" url = "" description = "" pubdate = "" for i in item: if i.tag == "title": title = i.text elif i.tag == "author" or i.tag.endswith("author"): author = i.text elif i.tag == "link": linkurl = i.text elif i.tag == "description": description = i.text elif i.tag == "pubDate": pubdate = i.text elif i.tag == "enclosure": url = i.get("url") elif i.tag.endswith("duration"): duration = i.text self.addItem(title, author, duration, type_of(url), description, pubdate, "", linkurl, url, "", "") else: self.seeXMLElement(dom) except Exception as e: logging.debug("ERR: " + str(e)) logging.debug("Parsing as HTML, not as XML.") ustart = self.source.find(" -1: # This is a redirect-page. newU = self.source[ustart+27:self.source.find("'", ustart+27)] self.Redirect = newU logging.debug("Parsing HTML") self.HTML = self.source dom = lxml.html.document_fromstring(self.source.replace(' 0: for i in locationelements[0]: if (type(i).__name__ == '_Element' and i.tag == "PathElement"): location.append(i.get("displayName")) locationLinks.append(i.text) if location == ["iTunes U"]: section = dom.xpath("//HBoxView") # looking for first section with location info. if len(section) > 0: # may be out of range section = section[0] for i in section: if (type(i).__name__ == '_Element'): for j in i: if type(j).__name__ == '_Element' and j.tag == "GotoURL": location.append(j.text.strip()) locationLinks.append(j.get("url")) logging.debug(j.text.strip() + j.get("url")) lastloc = j.get("url") if self.textContent(section).find(">") > -1: section.getparent().remove(section) # redundant section > section ... info is removed. if arr is None: ks = dom.xpath("/Document/Protocol/plist/dict/array/dict") if len(ks): arr = ks logging.debug("Special end page after html link?" + str(len(ks))) if (len(ks) == 1 and dom.get("disableNavigation") == "true" and dom.get("disableHistory") == "true"): self.singleItem = True logging.debug("tag " + dom.tag) if arr is None: # No tracklisting. hasmedia = False if len(self.mediaItems) == 0: logging.debug("nothing here!") else: # add the tracks: # TODO: Add XML page's elements to the top panel, so the bottom panel isn't necessary. hasmedia = True # for each item... for i in arr: if type(i).__name__ == '_Element' and i.tag == "dict": # for each track info.... get this information: name = "" artist = "" duration = "" comments = "" rtype = "" url = "" directurl = "" releaseDate = "" modifiedDate = "" id = "" for j in i: if j.tag == "key": # get each piece of data: if j.text in ["songName", "itemName"]: t = j.getnext().text if t: name = t elif j.text == "artistName": t = j.getnext().text if t: artist = t elif j.text == "duration": t = j.getnext().text if t: duration = t elif j.text in ["comments", "description", "longDescription"]: t = j.getnext().text if t: comments = t elif j.text == "url": t = j.getnext().text if t: url = t # Added Capital "URL", for the special case end page after html link. elif j.text in ["URL", "previewURL", "episodeURL", "preview-url"]: t = j.getnext().text if t: directurl = t elif j.text == "explicit": el = j.getnext() if el.text == "1": rtype = "[Explicit] " if el.text == "2": rtype = "[Clean] " elif j.text == "releaseDate": t = j.getnext().text if t: releaseDate = t elif j.text == "dateModified": t = j.getnext().text if t: modifiedDate = t elif j.text == "itemId": t = j.getnext().text if t: id = t elif j.text == "metadata": # for the special case end page after html link i.extend(j.getnext().getchildren()) # look inside this ... also. self.addItem(name, artist, time_convert(duration), type_of(directurl), rtype + comments, self.formatTime(releaseDate), self.formatTime(modifiedDate), url, directurl, "", id) # Now put page details in the detail-box on top. if dom.tag == "rss": out = "" image = dom.xpath("/rss/channel/image/url") if len(image) > 0: # get recommended width, height: w, h = None, None try: w = dom.xpath("/rss/channel/image/width")[0].text h = dom.xpath("/rss/channel/image/height")[0].text except: pass self.HTML += self.imgText(image[0].text, h, w) #else: # TODO: fix this namespace problem #image = dom.xpath("/rss/channel/itunes:image",namespaces={'itunes': 'http://www.itunes.com/DTDs/Podcast-1.0.dtd'})[0] #if len(image)>0... channel = dom.xpath("/rss/channel") if len(channel): for i in channel[0]: if not(image) and i.tag == "{http://www.itunes.com/dtds/podcast-1.0.dtd}image": self.HTML += self.imgText(i.get("href"), None, None) for i in channel[0]: if i.text and i.text.strip() != "" and isinstance(i.tag, str): thisname = "".join(i.tag.replace("{", "}").split("}")[::2]) # remove {....dtd} from tag self.HTML += "%s: %s\n
" % (thisname, i.text) try: self.Title = (dom.xpath("/rss/channel/title")[0].text) except IndexError as e: logging.warn('Error using index ' + str(e)) else: out = " > ".join(location) + "\n" self.Title = (out[:-1]) out = "" for i in range(len(location)): out += "
" + safe(location[i]) + " > " out = out[:-6] if dom.tag == "html": try: self.Title = dom.xpath("/html/head/title")[0].text_content() except IndexError as e: logging.warn('Error extracting title: ' + str(e)) self.Title = "TunesViewer" self.HTML = "" + self.HTML + "" # Get Podcast url # already have keys = dom.xpath("//key") self.podcast = "" if len(location) > 0 and location[0] == "Search Results": logging.debug("Search page, not podcast.") elif dom.tag == "rss": self.podcast = self.url elif hasmedia: for i in keys: if i.text == "feedURL": self.podcast = i.getnext().text # Get next text node's text. logging.debug("Podcast: " + self.podcast) break if self.podcast == "": #Last should have the page podcast url, with some modification. self.podcast = lastloc if lastloc == "": self.podcast = self.url if self.podcast.find("/Browse/") > -1: self.podcast = self.podcast.replace("/Browse/", "/Feed/") elif self.podcast.find("/BrowsePrivately/") > -1: self.podcast = self.podcast.replace("/BrowsePrivately/", "/Feed/") # If it's a protected podcast, it will have special goto-url: pbvs = dom.xpath("//PictureButtonView") for pbv in pbvs: if pbv.get("alt") == "Subscribe": self.podcast = pbv.getparent().get("draggingURL") else: logging.debug("Not a podcast page.") else: # not a podcast page? Check for html podcast feed-url in page: #Maybe redundant, with the subscribe links working. buttons = dom.xpath("//button") if len(buttons): isPod = True podurl = buttons[len(buttons)-1].get("feed-url") #the last feed-url, see if all feed-urls are this one. for b in buttons: if (b.get("feed-url") and b.get("feed-url") != podurl): #has feed-url, but it's different. isPod = False if isPod and podurl: # Every media file has link to same url, so it must be podcast url of this page. self.podcast = podurl elif (len(buttons) > 1 and buttons[0].get("subscribe-podcast-url")): if not(buttons[0].get("subscribe-podcast-url").startswith("http://itunes.apple.com/WebObjects/DZR.woa/wa/subscribePodcast?id=")): self.podcast = buttons[0].get("subscribe-podcast-url") logging.debug("Parse took " + str(time.time()-sttime) + "s.") # Done with this: del dom # avoid possible memory leak: http://faq.pygtk.org/index.py?req=show&file=faq08.004.htp gc.collect() if self.url.find("?i="): # link to specific item, select it. self.itemId = self.url[self.url.rfind("?i=")+3:] self.itemId = self.itemId.split("&")[0] logging.debug("Update took " + str(time.time()-sttime) + "seconds") def seeXMLElement(self, element): """ Recursively looks at xml elements. """ if isinstance(element.tag, str): # Good element, check this element: if element.get("backColor") and self.bgcolor == "": self.bgcolor = element.get("backColor") if element.tag == "GotoURL": urllink = element.get("url") name = self.textContent(element).strip() if element.get("draggingName"): author = element.get("draggingName") else: author = "" # See if there is text right after it for author. nexttext = element.getparent().getparent().getnext() match = re.match("Tab [0-9][0-9]* of [0-9][0-9]*", author) if match: # Tab handler logging.debug("ADDTAB " + match.group(0) + " " + urllink) match = author[match.end():] self.tabMatches.append(match) self.tabLinks.append(urllink) else: self.HTML += "" % element.get("url") for i in element: self.seeXMLElement(i) self.HTML += safe(element.text) + "" + safe(element.tail) elif element.tag == "FontStyle": if element.get("styleName") == "default": self.HTML += "" % \ (safe(element.get("color")), safe(element.get("font")), safe(element.get("size"))) elif element.tag == "HBoxView": self.HTML += "" for node in element: self.HTML += "" self.HTML += "
" self.seeXMLElement(node) self.HTML += "
" elif element.tag == "VBoxView": self.HTML += "" for node in element: self.HTML += "" self.HTML += "
" previousLen = len(self.HTML) self.seeXMLElement(node) if (len(self.HTML) == previousLen): self.HTML = self.HTML[:-8] # no empty row. else: self.HTML += "
" elif element.tag == "PictureView": if element.get("url"): self.HTML += self.imgText(element.get("url"), element.get("height"), element.get("width")) else: self.HTML += self.imgText(element.get("src"), element.get("height"), element.get("width")) for node in element: self.seeXMLElement(node) self.HTML += "" elif element.tag == "OpenURL": urllink = element.get("url") if urllink and urllink[0:4] != "itms": urllink = "WEB" + urllink name = self.textContent(element).strip() if element.get("draggingName"): author = element.get("draggingName") else: author = "" # See if there is text right after it for author. nexttext = element.getparent().getparent().getnext() # If there's a TextView-node right after, it should be the author-text or college name. if nexttext != None and isinstance(nexttext.tag, str) and nexttext.tag == "TextView": author = self.textContent(nexttext).strip() self.HTML += "%s" % (urllink, HTmarkup(name, False)) #if urllink and urllink[0:4]=="itms": #lnk = "(Link)" #else: #lnk = "(Web Link)" elif (element.tag == "key" and element.text == "action" and element.getnext() is not None): #Page action for redirect. #Key-val map is stored in namevalue(s) keymap = {} for node in element.getnext(): if node.tag == "key" and node.getnext() is not None: keymap[node.text] = node.getnext().text logging.debug(keymap) if ("kind" in keymap and keymap["kind"] in ["Goto", "OpenURL"] and "url" in keymap): self.Redirect = keymap["url"] elif (element.tag == "key" and element.getnext() is not None and element.text in ["message", "explanation", "customerMessage"]): self.HTML += "

%s

" % element.getnext().text elif (element.tag == "key" and element.getnext() is not None and element.text == "subscribe-podcast" and element.getnext().tag == "dict"): for node in element.getnext(): if (node.tag == "key" and node.getnext() is not None and node.text == "feedURL"): self.Redirect = node.getnext().text elif (element.tag == "key" and element.getnext() is not None and element.text == "kind" and element.getnext().text == "Perform" and element.getnext().getnext() is not None and element.getnext().getnext().text == "url" and element.getnext().getnext().getnext() is not None): self.Redirect = element.getnext().getnext().getnext().text logging.debug("REDIR" + self.Redirect) elif (element.tag == "Test" and (element.get("comparison").startswith("lt") or element.get("comparison") == "less")): pass #Ignore older version info, it would cause duplicates. elif (element.tag == "string" or (element.getprevious() is not None and element.getprevious().tag == "key" and element.tag != "dict") or element.tag in ["key", "MenuItem", "iTunes", "PathElement", "FontStyleSet"]): pass else: self.HTML += "<%s>" % element.tag if element.text and element.text.strip() != "": #Workaround for double text that is supposed to be shadow. #There is probably a better way to do this? if self.last_text.strip() != element.text.strip(): self.HTML += element.text self.last_text = element.text else: #same, ignore one. self.last_text = "" # Recursively see all elements: for node in element: self.seeXMLElement(node) self.HTML += "%s" % (element.tag, safe(element.tail)) def seeHTMLElement(self, element): if isinstance(element.tag, str): # normal element if (element.get("comparison") == "lt" or (element.get("comparison") and element.get("comparison").find("less") > -1)): return #Ignore child nodes. if element.tag == "tr" and element.get("dnd-clipboard-data"): import json data = json.loads(element.get("dnd-clipboard-data")) itemid = "" title = "" artist = "" duration = "" url = "" gotou = "" price = "0" comment = "" if ('itemName' in data): title = data['itemName'] if ('artistName' in data): artist = data['artistName'] if ('duration' in data): duration = time_convert(data['duration']) if ('preview-url' in data): url = data['preview-url'] if ('playlistName' in data): comment = data['playlistName'] if ('url' in data): gotou = data['url'] if ('price' in data): price = data['price'] if ('itemId' in data): itemid = data['itemId'] self.addItem(title, artist, duration, type_of(url), comment, "", "", gotou, url, price, itemid) elif (element.get("audio-preview-url") or element.get("video-preview-url") or element.get("episode-url")): if element.get("video-preview-url"): url = element.get("video-preview-url") elif element.get("episode-url"): url = element.get("episode-url") else: url = element.get("audio-preview-url") title = "" if element.get("preview-title"): title = element.get("preview-title") elif element.get("item-name"): title = element.get("item-name") author = "" if element.get("preview-artist"): author = element.get("preview-artist") elif element.get("artist-name"): author = element.get("artist-name") duration = "" if element.get("preview-duration"): duration = time_convert(element.get("preview-duration")) logging.debug("preview-url adding row") self.mediaItems.append([None, markup(title, False), author, duration, type_of(url), "", "", "", "", url, "", ""]) elif (element.tag == "button" and element.get("anonymous-download-url") and element.get("kind") and (element.get("title") or element.get("item-name"))):#Added for epub feature logging.debug("button row adding") title = "" artist = "" if element.get("title"): title = element.get("title") if element.get("item-name"): title = element.get("item-name") if element.get("preview-artist"): artist = element.get("preview-artist") self.addItem(title, artist, "", type_of(element.get("anonymous-download-url")), "", "", "", element.get("anonymous-download-url"), "", "", element.get("adam-id")) elif (element.tag == "button" and element.get("episode-url")): title = "" artist = "" url = "" itemid="" if element.get("aria-label"): title = element.get("aria-label") if title.startswith("Free Episode, "): title = title[14:] if element.get("artist-name"): artist = element.get("artist-name") if element.get("episode-url"): url = element.get("episode-url") mytype = type_of(url) if element.get("disabled") is not None: mytype = ".zip" # wrong ext. fix it. self.addItem(title, artist, "", mytype, "", "", "", "", url, "", itemid) elif element.tag=="script" and element.get("id")=="protocol" and element.get("type")=="text/x-apple-plist": print ''.join([etree.tostring(child) for child in element.iterdescendants()]) else: # go through the childnodes. for i in element: self.seeHTMLElement(i) def getItemsArray(self, dom): """Tries to get the array element of the dom, returns None if it doesn't exist.""" array = None els = dom.xpath("/Document/TrackList/plist/dict/key")#isinstance(i.tag,str) and i.tag == "key" and for i in els: #all childnodes: if i.text == "items": array = i.getnext() return array def addItem(self, title, author, duration, type, comment, releasedate, datemodified, gotourl, previewurl, price, itemid): """Adds item to media list.""" self.mediaItems.append([None, markup(title, False), author, duration, type, comment, releasedate, datemodified, gotourl, previewurl, price, itemid]) def textContent(self, element): """Gets all text content of the node.""" out = [] if type(element).__name__ == "_Element": if element.text: out.append(element.text) for i in element: out.append(self.textContent(i)) if i.tail: out.append(i.tail) return "".join(out) def formatTime(self, text): """Changes the weird DateTTimeZ format found in the xml date-time.""" return text.replace("T", " ").replace("Z", " ") def imgText(self, picurl, height, width): """Returns html for an image, given url, height, width.""" if height and width: return '' % (picurl, height, width) else: return '' % picurl tunesviewer-1.4.99.2/src/SingleWindowSocket.py000066400000000000000000000053011201655160300212570ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ A socket class to ensure only one Tunesviewer instance at one time. Copyright (C) 2009 - 2012 Luke Bryan 2011 - 2012 Rogério Theodoro de Brito and other contributors. 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. """ import logging import os import socket from threading import Thread import gobject from constants import DATA_SOCKET, DATA_DIR class SingleWindowSocket: """ Called on startup to emulate a singleton. A socket file is one of the ways to makes sure there is only one instance of the program. Otherwise, it will mess up downloads when they both download to the same file. """ def __init__(self, url, main): self.caller = main self.RUN = False # When true, start program. try: os.makedirs(DATA_DIR) except OSError as e: # Errno 17 means something already exists if e.errno == 17 and os.path.isdir(DATA_DIR): # No problem pass elif e.errno == 17 and not os.path.isdir(DATA_DIR): logging.warn('%s already exists, but is not a directory: ' + str(e)) else: logging.warn('Error creating data directory: ' + str(e)) if os.path.exists(DATA_SOCKET): try: self.sendUrl(url) except socket.error as msg: logging.error("Possible stale socket (%s). Starting server." % str(msg)) os.remove(DATA_SOCKET) self.RUN = True Thread(target=self.server).start() else: self.RUN = True Thread(target = self.server).start() def sendUrl(self, url): """ Sends to currently running instance. """ s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) s.connect(DATA_SOCKET) s.send(url) s.close() def server(self): """ Listens for urls and loads in this process. """ s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) s.settimeout(None) # important! Otherwise default timeout will apply. try: os.mkdir(DATA_DIR) except OSError as e: if e.errno == 17: pass else: logging.error('Error creating socket: %s.' % str(e)) s.bind(DATA_SOCKET) while True: url = s.recv(65536) # Wait for a url to load. if url == 'EXIT': os.remove(DATA_SOCKET) return # End this thread to let program exit normally. gobject.idle_add(self.caller.gotoURL, url, True) gobject.idle_add(self.caller.window.present) tunesviewer-1.4.99.2/src/Throbber.gif000066400000000000000000000014711201655160300173650ustar00rootroot00000000000000GIF89a333LLLfff! NETSCAPE2.0! ,HɉZgՆ}HIl ta ޭpe0 &|.Q^ڭ"'N! ,HBZ}HIl0tq<ޭp@ &|.BQ^ڭ"'N! ,HZ}HIlAt<ޭpP&|.Q^ڭ"'N! ,HƠZ'!}HIlTQtS<ޭp`&|.Q^ڭ"'N! ,HZg) }HIlat <ޭpp&|.Q^ڭ"'N! ,HJZ1}HIlqt0<ޭp&|.@Q^ڭ"'N! ,HZ9}HIltA<ޭp% &|.Q^ڭ"'N! ,HΡZ'}HIlTtSQ<ޭpE &|.Q^ڭ"'N;tunesviewer-1.4.99.2/src/TunesViewer.py000077500000000000000000001502211201655160300177620ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ TunesViewer A small, easy-to-use tool to access iTunesU and podcast media. Copyright (C) 2009 - 2012 Luke Bryan 2011 - 2012 Rogério Theodoro de Brito and other contributors. 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. """ # Import standard Python modules import cookielib import gzip import logging import os import socket import subprocess import time import urllib import urllib2 from StringIO import StringIO from threading import Thread # Import third-party modules (GTK and siblings for GUI) import glib import gobject import gtk import pango gobject.threads_init() from lxml import etree # Import local project modules from configbox import ConfigBox from findinpagebox import FindInPageBox from downloadbox import DownloadBox from findbox import FindBox from itemdetails import ItemDetails from webkitview import WebKitView from Parser import Parser from SingleWindowSocket import SingleWindowSocket from common import * from constants import TV_VERSION, SEARCH_U, SEARCH_P, USER_AGENT, HELP_URL, BUG_URL class TunesViewer: source = "" # full html/xml source url = "" # page url podcast = "" # podcast url pageType = "" # text/xml or text/html downloading = False # when true, don't download again, freezing # prog. (Enforces having ONE gotoURL) downloadError = "" infoboxes = [] redirectPages = [] # Lists can be used as stacks: just use list.append(...) and list.pop() # Stacks for back and forward buttons: backStack = [] forwardStack = [] # Initializes the main window def __init__(self, dname=None): self.downloadbox = DownloadBox(self) # Only one downloadbox is constructed self.findbox = FindBox(self) self.findInPage = FindInPageBox() self.findInPage.connect('find', self.find_in_page_cb) # Create a new window, initialize all widgets: self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("TunesViewer") self.window.set_size_request(350, 350) #minimum self.window.resize(750, 750) #default self.window.connect("delete_event", self.delete_event) # set add drag-drop, based on: # http://stackoverflow.com/questions/1219863/python-gtk-drag-and-drop-get-url self.window.drag_dest_set(0, [], 0) self.window.connect('drag_motion', self.motion_cb) self.window.connect('drag_drop', self.drop_cb) self.window.connect('drag_data_received', self.got_data_cb) try: icon_app_path = '/usr/share/icons/hicolor/scalable/apps/tunesview.svg' pixbuf = gtk.gdk.pixbuf_new_from_file(icon_app_path) self.window.set_icon(pixbuf) except: logging.warn("Couldn't load window icon.") # will hold icon, title, artist, time, type, comment, releasedate, datemodified, gotourl, previewurl, price, itemid. self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, str, str, str, str, str, str, str) #Liststore goes in a TreeView inside a Scrolledwindow: self.treeview = gtk.TreeView(model=self.liststore) self.treeview.set_enable_search(True) self.scrolledwindow = gtk.ScrolledWindow() self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrolledwindow.add(self.treeview) cell = gtk.CellRendererText() # Set the cell-renderer to ellipsize ... at end. cell.set_property("ellipsize", pango.ELLIPSIZE_END) # Set single-paragraph-mode, no huge multiline display rows. cell.set_property("single-paragraph-mode", True) col = gtk.TreeViewColumn(" ") # col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) pb = gtk.CellRendererPixbuf() col.pack_start(pb) col.add_attribute(pb, 'pixbuf', 0) self.treeview.append_column(col) # Now each column is created and added: col = gtk.TreeViewColumn("Name") col.pack_start(cell) col.add_attribute(cell, 'markup', 1) # because markup, not text, is required in tooltip. col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) col.set_fixed_width(200) col.set_expand(True) col.set_resizable(True) col.set_reorderable(True) # col.set_max_width(100) self.treeview.set_search_column(1) col.set_sort_column_id(1) # col.set_property('resizable', 1) self.treeview.append_column(col) self.treeview.set_tooltip_column(1) # needs markup, not text col = gtk.TreeViewColumn("Author") col.pack_start(cell) col.add_attribute(cell, 'text', 2) col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) #col.set_expand(True) col.set_fixed_width(150) col.set_resizable(True) col.set_reorderable(True) self.treeview.set_search_column(2) col.set_sort_column_id(2) self.treeview.append_column(col) time_cell = gtk.CellRendererText() col = gtk.TreeViewColumn("Time") col.pack_start(time_cell) time_cell.set_property('xalign', 1.0) col.add_attribute(time_cell, 'text', 3) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) col.set_resizable(True) col.set_reorderable(True) self.treeview.set_search_column(3) col.set_sort_column_id(3) self.treeview.append_column(col) col = gtk.TreeViewColumn("Type") col.pack_start(cell) col.add_attribute(cell, 'text', 4) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) col.set_resizable(True) col.set_reorderable(True) self.treeview.set_search_column(4) col.set_sort_column_id(4) self.treeview.append_column(col) col = gtk.TreeViewColumn("Comment") col.pack_start(cell) col.add_attribute(cell, 'text', 5) col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) col.set_expand(True) col.set_min_width(100) col.set_resizable(True) col.set_reorderable(True) self.treeview.set_search_column(5) col.set_sort_column_id(5) self.treeview.append_column(col) self.treeview.set_search_column(0) col = gtk.TreeViewColumn("Release Date") col.pack_start(cell) col.add_attribute(cell, 'text', 6) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) col.set_resizable(True) col.set_reorderable(True) self.treeview.set_search_column(6) col.set_sort_column_id(6) self.treeview.append_column(col) self.treeview.set_search_column(0) col = gtk.TreeViewColumn("Modified Date") col.pack_start(cell) col.add_attribute(cell, 'text', 7) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) col.set_resizable(True) col.set_reorderable(True) self.treeview.set_search_column(7) col.set_sort_column_id(7) self.treeview.append_column(col) self.treeview.set_search_column(1) self.treeview.connect("row-activated", self.rowSelected) self.treeview.get_selection().set_select_function(self.treesel, data=None) # Make the locationbar-searchbar: locationbox = gtk.HBox() self.modecombo = gtk.combo_box_new_text() self.modecombo.append_text("Url:") self.modecombo.append_text("iTunesU Search:") self.modecombo.append_text("Podcast Search:") self.modecombo.set_active(1) self.modecombo.connect("changed", self.combomodechanged) locationbox.pack_start(self.modecombo, False, False, 0) self.locationentry = gtk.Entry() self.locationentry.connect("activate", self.gobutton) locationbox.pack_start(self.locationentry, True, True, 0) gobutton = gtk.Button(label="Go", stock=gtk.STOCK_OK) # set GO label # http://www.harshj.com/2007/11/17/setting-a-custom-label-for-a-button-with-stock-icon-in-pygtk/ try: gobutton.get_children()[0].get_children()[0].get_children()[1].set_label("G_O") except: pass gobutton.connect("clicked", self.gobutton) locationbox.pack_start(gobutton, False, False, 0) prefheight = self.locationentry.size_request()[1] #Default button is very tall, try to make buttons the same height as the text box: gobutton.set_size_request(-1, prefheight) # Now make menus: http://zetcode.com/tutorials/pygtktutorial/menus/ agr = gtk.AccelGroup() self.window.add_accel_group(agr) menubox = gtk.HBox() mb = gtk.MenuBar() menubox.pack_start(mb, 1, 1, 0) self.throbber = gtk.Image() throbber_path = '/usr/share/tunesviewer/Throbber.gif' try: self.throbber.set_from_animation(gtk.gdk.PixbufAnimation(throbber_path)) menubox.pack_start(self.throbber, expand=False) except glib.GError as e: logging.error('Could not set the throbber: %s' % str(e)) ### Top level 'File' menu filemenu = gtk.Menu() filem = gtk.MenuItem("_File") filem.set_submenu(filemenu) ## Advanced search aSearch = gtk.ImageMenuItem(gtk.STOCK_FIND) aSearch.set_label("Advanced _Search...") aSearch.connect("activate", self.advancedSearch) key, mod = gtk.accelerator_parse("K") aSearch.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) filemenu.append(aSearch) ## Search on current page pSearch = gtk.ImageMenuItem(gtk.STOCK_FIND) pSearch.set_label("Find on Current Page...") pSearch.connect("activate", self.searchCurrent) key, mod = gtk.accelerator_parse("F") pSearch.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) filemenu.append(pSearch) filemenu.append(gtk.SeparatorMenuItem()) ## Exit application exit = gtk.ImageMenuItem(gtk.STOCK_QUIT) exit.set_label("E_xit") exit.connect("activate", self.exitclicked) key, mod = gtk.accelerator_parse("Q") exit.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) filemenu.append(exit) ### Edit menu editmenu = gtk.Menu() editm = gtk.MenuItem("_Edit") editm.set_submenu(editmenu) ## Copy podcast URL self.copym = gtk.ImageMenuItem(gtk.STOCK_COPY) self.copym.set_label("_Copy Normal Podcast Url") self.copym.connect("activate", self.copyrss) key, mod = gtk.accelerator_parse("C") self.copym.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) editmenu.append(self.copym) ## Paste URL pastem = gtk.ImageMenuItem(gtk.STOCK_PASTE) pastem.set_label("Paste and _Goto Url") pastem.connect("activate", self.pastego) key, mod = gtk.accelerator_parse("V") pastem.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) editmenu.append(pastem) editmenu.append(gtk.SeparatorMenuItem()) ## Preferences prefs = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES) prefs.connect("activate", self.openprefs) editmenu.append(prefs) ### View menu viewmenu = gtk.Menu() viewm = gtk.MenuItem("_View") viewm.set_submenu(viewmenu) ## Request content in HTML mode self.htmlmode = gtk.CheckMenuItem("Request _HTML Mode") self.htmlmode.set_active(True) viewmenu.append(self.htmlmode) self.mobilemode = gtk.CheckMenuItem("Mobile Mode") viewmenu.append(self.mobilemode) viewmenu.append(gtk.SeparatorMenuItem()) ## Show downloads viewdownloads = gtk.ImageMenuItem(gtk.STOCK_GO_DOWN) viewdownloads.set_label("Show _Downloads") key, mod = gtk.accelerator_parse("j") viewdownloads.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) viewdownloads.connect("activate", self.viewDownloads) viewmenu.append(viewdownloads) ## Open downloads directory viewdir = gtk.ImageMenuItem(gtk.STOCK_DIRECTORY) viewdir.set_label("Downloads Directory") viewdir.connect("activate", self.openDownloadDir) viewmenu.append(viewdir) viewmenu.append(gtk.SeparatorMenuItem()) ## Zoom in ziItem = gtk.ImageMenuItem(gtk.STOCK_ZOOM_IN) key, mod = gtk.accelerator_parse("plus") ziItem.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) ziItem.connect("activate", self.webkitZI) viewmenu.append(ziItem) ## Zoom out zoItem = gtk.ImageMenuItem(gtk.STOCK_ZOOM_OUT) key, mod = gtk.accelerator_parse("minus") zoItem.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) zoItem.connect("activate", self.webkitZO) viewmenu.append(zoItem) ## Reset zoom znItem = gtk.ImageMenuItem(gtk.STOCK_ZOOM_100) key, mod = gtk.accelerator_parse("0") znItem.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) znItem.connect("activate", self.webkitZN) viewmenu.append(znItem) viewmenu.append(gtk.SeparatorMenuItem()) ## View page source viewsource = gtk.MenuItem("Page _Source") key, mod = gtk.accelerator_parse("U") viewsource.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) viewsource.connect("activate", self.viewsource) viewmenu.append(viewsource) ## View cookies viewcookie = gtk.MenuItem("_Cookies") viewcookie.connect("activate", self.viewCookie) viewmenu.append(viewcookie) ## View information of selected item viewprop = gtk.ImageMenuItem(gtk.STOCK_INFO) viewprop.set_label("Selection _Info") key, mod = gtk.accelerator_parse("I") viewprop.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) viewmenu.append(viewprop) viewprop.connect("activate", self.viewprop) self.locShortcut = gtk.MenuItem("Current _URL") key, mod = gtk.accelerator_parse("L") self.locShortcut.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) viewmenu.append(self.locShortcut) self.locShortcut.hide() self.locShortcut.connect("activate", self.locationBar) ### Go menu gomenu = gtk.Menu() gom = gtk.MenuItem("_Go") gom.set_submenu(gomenu) ## iTunes U subdirectory self.itunesuDir = gtk.Menu() itunesu = gtk.MenuItem("iTunes_U") itunesu.set_submenu(self.itunesuDir) #self.itunesuDir.append(gtk.MenuItem("directory here")) gomenu.append(itunesu) ## Podcast subdirectory self.podcastDir = gtk.Menu() podcasts = gtk.MenuItem("_Podcasts") podcasts.set_submenu(self.podcastDir) #self.podcastDir.append(gtk.MenuItem("directory here")) gomenu.append(podcasts) ## Go back back = gtk.ImageMenuItem(gtk.STOCK_GO_BACK) key, mod = gtk.accelerator_parse("Left") back.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) back.connect("activate", self.goBack) gomenu.append(back) ## Go forward forward = gtk.ImageMenuItem(gtk.STOCK_GO_FORWARD) key, mod = gtk.accelerator_parse("Right") forward.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) forward.connect("activate", self.goForward) gomenu.append(forward) ## Refresh page refresh = gtk.ImageMenuItem(gtk.STOCK_REFRESH) key, mod = gtk.accelerator_parse("F5") refresh.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) refresh.connect("activate", self.refresh) gomenu.append(refresh) ## Stop loading stop = gtk.ImageMenuItem(gtk.STOCK_STOP) key, mod = gtk.accelerator_parse("Escape") stop.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) stop.connect("activate", self.stop) gomenu.append(stop) ## Go to the initial page homeb = gtk.ImageMenuItem(gtk.STOCK_HOME) homeb.connect("activate", self.goHome) gomenu.append(homeb) ### Actions menu itemmenu = gtk.Menu() itemm = gtk.MenuItem("_Actions") itemm.set_submenu(itemmenu) ## Go to link follow = gtk.ImageMenuItem(gtk.STOCK_JUMP_TO) follow.connect("activate", self.followlink) follow.set_label("_Goto Link") key, mod = gtk.accelerator_parse("G") follow.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) itemmenu.append(follow) ## Play/View file playview = gtk.ImageMenuItem(gtk.STOCK_MEDIA_PLAY) playview.set_label("_Play/View File") playview.connect("activate", self.playview) key, mod = gtk.accelerator_parse("P") playview.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) itemmenu.append(playview) ## Download file download = gtk.ImageMenuItem(gtk.STOCK_SAVE) download.set_label("_Download File") download.connect("activate", self.download) key, mod = gtk.accelerator_parse("D") download.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) itemmenu.append(download) ## Add Page to podcast manager self.addpodmenu = gtk.ImageMenuItem(gtk.STOCK_ADD) self.addpodmenu.set_label("_Add Page to Podcast Manager") self.addpodmenu.connect("activate", self.addPod) itemmenu.append(self.addpodmenu) ### Contextual (right-click) menu self.rcmenu = gtk.Menu() self.rcgoto = gtk.ImageMenuItem(gtk.STOCK_JUMP_TO) self.rcgoto.connect("activate", self.followlink) self.rcgoto.set_label("_Goto") self.rcgoto.show() self.rccopy = gtk.ImageMenuItem(gtk.STOCK_COPY) self.rccopy.connect("activate", self.copyRowLink) self.rccopy.set_label("_Copy Link") self.rccopy.show() self.rcplay = gtk.ImageMenuItem(gtk.STOCK_MEDIA_PLAY) self.rcplay.connect("activate", self.playview) self.rcplay.set_label("_Play/View") self.rcplay.show() self.rcdownload = gtk.ImageMenuItem(gtk.STOCK_SAVE) self.rcdownload.connect("activate", self.download) self.rcdownload.set_label("_Download") self.rcdownload.show() rcinfo = gtk.ImageMenuItem(gtk.STOCK_INFO) rcinfo.connect("activate", self.viewprop) rcinfo.set_label("Item _Info") rcinfo.show() self.rcmenu.append(rcinfo) self.rcmenu.append(self.rcgoto) self.rcmenu.append(self.rccopy) self.rcmenu.append(self.rcplay) self.rcmenu.append(self.rcdownload) self.treeview.connect("button_press_event", self.treeclick) ### Help menu helpmenu = gtk.Menu() helpm = gtk.MenuItem("_Help") helpm.set_submenu(helpmenu) ## Help helpitem = gtk.ImageMenuItem(gtk.STOCK_HELP) helpitem.connect("activate", self.showHelp) helpmenu.append(helpitem) ## Check for updates helpupdate = gtk.MenuItem("Check for _Update...") helpupdate.connect("activate", self.progUpdate) helpmenu.append(helpupdate) ## Report a bug helpreport = gtk.MenuItem("Report a Bug...") helpreport.connect("activate", self.bugReport) helpmenu.append(helpreport) ## About the program helpabout = gtk.MenuItem("About") helpabout.connect("activate", self.showAbout) helpmenu.append(helpabout) #Set up the main menu: mb.append(filem) mb.append(editm) mb.append(viewm) mb.append(gom) mb.append(itemm) mb.append(helpm) #Location buttons: (University > section > title) self.locationhbox = gtk.HBox() #self.locationhbox.set_size_request(prefheight,-1) self.locationhbox.pack_start(gtk.Label(" Media files on this page: "), False, False, 0) #This will hold references to the buttons in this box: #self.locationbuttons = [] not needed self.notebook = gtk.Notebook() self.notebook.set_property("enable-popup", True) self.notebook.connect_after("switch-page", self.tabChange) #important to connect-after! Normal connect will cause display problems and sometimes segfault. self.notebook.set_scrollable(True) self.notebookbox = gtk.HBox() self.notebookbox.pack_start(self.notebook, True, True, 0) bottom = gtk.VBox() bottom.pack_start(self.locationhbox, False, False, 2) bottom.pack_start(self.notebookbox, False, False, 2) bottom.pack_start(self.scrolledwindow, True, True, 2) # adjustable panel with description box above listing: vpaned = gtk.VPaned() vpaned.set_position(500) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.descView = WebKitView(self) sw.add(self.descView) vpaned.pack1(sw, resize=True) vpaned.pack2(bottom, resize=False) self.toolbar = gtk.Toolbar() self.tbBack = gtk.ToolButton(gtk.STOCK_GO_BACK) self.tbBack.connect("clicked", self.goBack) self.tbBack.set_tooltip_text("Back") self.toolbar.insert(self.tbBack, -1) self.tbForward = gtk.ToolButton(gtk.STOCK_GO_FORWARD) self.tbForward.connect("clicked", self.goForward) self.tbForward.set_tooltip_text("Forward") self.toolbar.insert(self.tbForward, -1) tbRefresh = gtk.ToolButton(gtk.STOCK_REFRESH) tbRefresh.connect("clicked", self.refresh) tbRefresh.set_tooltip_text("Refresh Page") self.toolbar.insert(tbRefresh, -1) self.tbStop = gtk.ToolButton(gtk.STOCK_STOP) self.tbStop.connect("clicked", self.stop) self.tbStop.set_tooltip_text("Stop") self.toolbar.insert(self.tbStop, -1) self.toolbar.insert(gtk.SeparatorToolItem(), -1) opendl = gtk.ToolButton(gtk.STOCK_DIRECTORY) opendl.set_tooltip_text("Open Downloads Directory") opendl.connect("clicked", self.openDownloadDir) self.toolbar.insert(opendl, -1) self.toolbar.insert(gtk.SeparatorToolItem(), -1) self.tbInfo = gtk.ToolButton(gtk.STOCK_INFO) self.tbInfo.connect("clicked", self.viewprop) self.tbInfo.set_tooltip_text("View Selection Information") self.toolbar.insert(self.tbInfo, -1) self.tbGoto = gtk.ToolButton(gtk.STOCK_JUMP_TO) self.tbGoto.connect("clicked", self.followlink) self.tbGoto.set_tooltip_text("Goto Link") self.toolbar.insert(self.tbGoto, -1) self.tbPlay = gtk.ToolButton(gtk.STOCK_MEDIA_PLAY) self.tbPlay.connect("clicked", self.playview) self.tbPlay.set_tooltip_text("Play/View File") self.toolbar.insert(self.tbPlay, -1) self.tbDownload = gtk.ToolButton(gtk.STOCK_SAVE) self.tbDownload.connect("clicked", self.download) self.tbDownload.set_tooltip_text("Download File") self.toolbar.insert(self.tbDownload, -1) self.toolbar.insert(gtk.SeparatorToolItem(), -1) self.tbCopy = gtk.ToolButton(gtk.STOCK_COPY) self.tbCopy.connect("clicked", self.copyrss) self.tbCopy.set_tooltip_text("Copy Normal Podcast Url") self.toolbar.insert(self.tbCopy, -1) self.tbAddPod = gtk.ToolButton(gtk.STOCK_ADD) self.tbAddPod.set_tooltip_text("Add to Podcast Manager") self.tbAddPod.connect("clicked", self.addPod) self.toolbar.insert(self.tbAddPod, -1) self.toolbar.insert(gtk.SeparatorToolItem(), -1) tbFind = gtk.ToolButton(gtk.STOCK_FIND) tbZO = gtk.ToolButton(gtk.STOCK_ZOOM_OUT) tbZO.connect("clicked", self.webkitZO) self.toolbar.insert(tbZO, -1) tbZI = gtk.ToolButton(gtk.STOCK_ZOOM_IN) tbZI.connect("clicked", self.webkitZI) self.toolbar.insert(tbZI, -1) self.toolbar.insert(gtk.SeparatorToolItem(), -1) tbFind.set_tooltip_text("Advanced Search") tbFind.connect("clicked", self.advancedSearch) self.toolbar.insert(tbFind, -1) spacer = gtk.SeparatorToolItem() spacer.set_draw(0) spacer.set_expand(1) self.toolbar.insert(spacer, -1) self.tbAuth = gtk.ToolButton(gtk.STOCK_DIALOG_AUTHENTICATION) self.toolbar.insert(self.tbAuth, -1) self.toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) # All those objects go in the main vbox: self.mainvbox = gtk.VBox() self.mainvbox.pack_start(menubox, False, False, 0) self.mainvbox.pack_start(self.toolbar, False, False, 0) self.mainvbox.pack_start(locationbox, False, False, 0) #self.mainvbox.pack_start(self.scrolledwindow, True, True, 0) self.mainvbox.pack_start(vpaned, True, True, 0) self.statusbar = gtk.Label() self.statusbar.set_justify(gtk.JUSTIFY_LEFT) self.mainvbox.pack_start(self.statusbar, False, True, 0) #self.window.set_property('allow-shrink', True) self.updateBackForward() self.window.add(self.mainvbox) self.window.show_all() #Start focus on the entry: self.window.set_focus(self.locationentry) # Disable the copy/add podcast until valid podcast is here. self.tbCopy.set_sensitive(self.podcast != "") self.tbAddPod.set_sensitive(self.podcast != "") self.addpodmenu.set_sensitive(self.podcast != "") self.copym.set_sensitive(self.podcast != "") self.tbAuth.hide() self.noneSelected() self.config = ConfigBox(self) # Only one configuration box, it has reference back to here to change toolbar,statusbar settings. # Set up the main url handler with downloading and cookies: self.cj = cookielib.CookieJar() self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj)) self.opener.addheaders = [('User-agent', self.descView.ua), ('Accept-Encoding', 'gzip'), ('Accept-Language', 'en-US')] def webkitZI(self, obj): self.descView.zoom_in() def webkitZO(self, obj): self.descView.zoom_out() def webkitZN(self, obj): self.descView.set_zoom_level(1) def buttonGoto(self, obj, url): "Menu directory-shortcuts handler" self.gotoURL(url, True) def goHome(self, obj): self.gotoURL(self.config.home, True) def getDirectory(self): """Sets up quick links in the Go menu. This info appears to have been moved to " % self.injectJavascript), url_to_load) self.webkitLoading = False def webkitGo(self, view, frame, net_req, nav_act, pol_dec): logging.debug("webkit-request.") logging.debug(str(net_req)) if not self.webkitLoading: # Don't load in browser, let this program download/convert it... logging.debug("Noload") logging.debug(net_req.get_uri()) self.opener.gotoURL(net_req.get_uri(), True) return True tunesviewer-1.4.99.2/tests/000077500000000000000000000000001201655160300154775ustar00rootroot00000000000000tunesviewer-1.4.99.2/tests/testCommon.py000066400000000000000000000073371201655160300202130ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Copyright (C) 2009 - 2012 Luke Bryan 2011 - 2012 Rogério Theodoro de Brito and other contributors. 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. """ import sys import unittest sys.path.append('src') sys.path.append('../src') from common import * class TestCommon(unittest.TestCase): def testTimeConvert(self): self.assertEqual(time_convert(1000), "0:01") self.assertEqual(time_convert(1000 * 60), "1:00") self.assertEqual(time_convert(1000 * 61), "1:01") self.assertEqual(time_convert(1000 * 70), "1:10") self.assertEqual(time_convert(1000 * 3599), "59:59") self.assertEqual(time_convert(1000 * 3600), "1:00:00") self.assertEqual(time_convert(1000 * 3601), "1:00:01") self.assertEqual(time_convert(1000 * 3660), "1:01:00") self.assertEqual(time_convert(1000 * 3661), "1:01:01") self.assertEqual(time_convert(1000 * 7200), "2:00:00") self.assertEqual(time_convert('30000.0'), "0:30") self.assertEqual(time_convert("bogus"), "bogus") self.assertEqual(time_convert([]), []) def testHTML(self): self.assertEqual(htmlentitydecode("M&M"), "M&M") self.assertEqual(htmlentitydecode("<"), "<") self.assertEqual(htmlentitydecode("<"), "<") self.assertEqual(htmlentitydecode("dynlists©"), "dynlists©") self.assertEqual(htmlentitydecode("dynlists©"), "dynlists©") def testSafeFilename(self): basename = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 $%`-_@{}~!#()." good = "/home/" + basename self.assertEqual(safeFilename("/home/mydirectory/somefile", True), "somefile") self.assertEqual(safeFilename(good, True), good[6:]) # without /home/. def testSuperUnquote(self): self.assertEqual(super_unquote('. Automated Testing'), '. Automated Testing') self.assertEqual(super_unquote('.%20Automated%20Testing'), '. Automated Testing') self.assertEqual(super_unquote('.%2520Automated%2520Testing'), '. Automated Testing') def testDesc(self): self.assertEqual(desc(1), '1.0 B') self.assertEqual(desc(512), '512.0 B') self.assertEqual(desc(1023), '1023.0 B') self.assertEqual(desc(1024), '1.0 KB') self.assertEqual(desc(1025), '1.0 KB') self.assertEqual(desc(512 * 1024), '512.0 KB') self.assertEqual(desc(1023 * 1024), '1023.0 KB') self.assertEqual(desc(1024 * 1024), '1.0 MB') self.assertEqual(desc(1025 * 1024), '1.0 MB') self.assertEqual(desc(0.5 * 1024 * 1024 * 1024), '512.0 MB') self.assertEqual(desc(10000000000000000000),"(way too big)") def testTypeOf(self): self.assertEqual(type_of("http://a1135.g.akamai.net/f/1135/18227/1h/cchannel.download.akamai.com/18227/podcast/NEWYORK-NY/WAXQ-FM/Secretariat1.mp3?CPROG=PCAST&MARKET=NEWYORK-NY&NG_FORMAT=&SITE_ID=1674&STATION_ID=WAXQ-FM&PCAST_AUTHOR=Q104.3_New_York_City&PCAST_CAT=comedy&PCAST_TITLE=Jim_Kerr_Rock_and_Roll_Morning_Show_Parodies"),".mp3") self.assertEqual(type_of("http://a1135.g.akamai.net/f/1135/18227/1h/cchannel.download.akamai.com/18227/podcast/NEWYORK-NY/WAXQ-FM/ACN_NFLFilmsARealSport_-34414.mp3?CPROG=PCAST&MARKET=NEWYORK-NY&NG_FORMAT=&SITE_ID=1674&STATION_ID=WAXQ-FM&PCAST_AUTHOR=Q104.3_New_York_City&PCAST_CAT=comedy&PCAST_TITLE=Jim_Kerr_Rock_and_Roll_Morning_Show_Parodies"),".mp3") self.assertEqual(type_of("simplefilename.mp4"),".mp4") if __name__ == "__main__": unittest.main() tunesviewer-1.4.99.2/tests/testParser.py000066400000000000000000000171401201655160300202100ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Copyright (C) 2009 - 2012 Luke Bryan 2011 - 2012 Rogério Theodoro de Brito and other contributors. 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. """ import logging import sys import unittest import urllib2 sys.path.append('src') sys.path.append('../src') from Parser import Parser from constants import USER_AGENT # Test the parser class - Requires network! class TestParser(unittest.TestCase): def setUp(self): """ Take care of the preparation of the tests. """ self.o = urllib2.build_opener() self.o.addheaders = [('User-agent', USER_AGENT)] def checkHTML(self,html): """ Checks html, makes sure important layout tags are matching with end tags. """ for tag in ["td","tr","div"]: #print tag, html.count("<%s>" % tag)+html.count("<%s "), html.count("" % tag) self.assertEqual(html.count("<%s>" % tag)+html.count("<%s "),html.count("" % tag)) def checkItems(self,mediaItems): for line in mediaItems: self.assertEqual(len(line[4]), 4) # test 4-char extension. self.assertEqual(line[4][0], ".") self.assertEqual(len(line[3]), 7) # test h:mm:ss time length. def testParseAgriculture(self): url = "http://itunes.apple.com/WebObjects/DZR.woa/wa/viewPodcast?cc=us&id=387961518" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'Food and Sustainable Agriculture') self.assertEqual(len(parsed_html.mediaItems), 7) # FIXME: The following should be made into proper tests for line in parsed_html.mediaItems: logging.debug(line) def testParseGeorgeFox(self): url = "https://deimos.apple.com/WebObjects/Core.woa/BrowsePrivately/georgefox.edu.01651902695" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) # FIXME: Maybe it could be smarter about finding the title... self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iTunes U > Top Downloads') self.assertEqual(len(parsed_html.mediaItems), 0) # Not sure where the bogus element is coming from in the gui... self.checkItems(parsed_html.mediaItems) def testMain(self): url = "http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewGrouping?id=27753" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) # FIXME: Maybe it could be smarter about finding the title... self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iTunes U') self.checkItems(parsed_html.mediaItems) def testParseFHSU(self): url = "http://deimos.apple.com/WebObjects/Core.woa/Browse/fhsu.edu" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) # FIXME: Maybe it could be smarter about finding the title... self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iTunes U') self.assertEqual(len(parsed_html.mediaItems), 0) self.checkItems(parsed_html.mediaItems) def testParseFHSUPresident(self): url = "http://deimos.apple.com/WebObjects/Core.woa/Browse/fhsu.edu.1152205441" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) # FIXME: Maybe it could be smarter about finding the title... self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iTunes U > Fort Hays State University > ' 'FHSU News > From the President - ' 'President Hammond') self.assertEqual(len(parsed_html.mediaItems), 30) # FIXME: The following should be made into proper tests for line in parsed_html.mediaItems: logging.debug(line) def testParseSIUC(self): url = "http://deimos3.apple.com/WebObjects/Core.woa/Browse/siuc.edu?ignore.mscache=9669968" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) # FIXME: Maybe it could be smarter about finding the title... self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iTunes U') self.assertEqual(len(parsed_html.mediaItems), 0) self.checkHTML(parsed_html.HTML) self.assertEqual(parsed_html.HTML.count(""),parsed_html.HTML.count("")) self.assertEqual(parsed_html.HTML.count(""),parsed_html.HTML.count("")) # FIXME: The following should be made into proper tests self.checkItems(parsed_html.mediaItems) def testParseSJSU(self): url = "http://deimos3.apple.com/WebObjects/Core.woa/Browse/sjsu.edu?ignore.mscache=3176353" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) # FIXME: Maybe it could be smarter about finding the title... self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iTunes U') self.assertEqual(len(parsed_html.mediaItems), 0) self.checkItems(parsed_html.mediaItems) def testParseWithTabs(self): url = "https://deimos.apple.com/WebObjects/Core.woa/BrowsePrivately/georgefox.edu.1285568794" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iTunes U > George Fox University > Chapel - Chapel 2012 - 2013') # FIXME: Are all tabs shown? self.assertEqual(parsed_html.tabMatches, [', Selected. Chapel 2012 - 2013', '. Chapel 2011-2012', '. Shalom 2011-2012', '. Chapel 2010-2011', '. Shalom 2010-2011', '. Other 2010-2011', '. Chapel 2009-2010', '. Shalom 2009-2010', '. Chapel 2008-2009', '. Shalom 2008-2009', '. Chapel 2007 - 2008', '. Chapel 2006-2007', '. Chapel 2005-2006', '. Chapel 2004-2005', '. Chapel 2003-2004', '. Chapel 2002-2003', '. Chapel 2001-2002', '. Chapel 2000-2001']) def testWebRedirect(self): url = "http://www2.ohlone.edu/cgi-bin/itunespub/itunes_public.pl" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text) assert parsed_html.Redirect.startswith('itmss://deimos.apple.com/WebObjects/Core.woa/BrowsePrivately/ohlone.edu') def test_XML_feed(self): url = "https://deimos.apple.com/WebObjects/Core.woa/Feed/itunes.stanford.edu-dz.11153667080.011153667082" text = self.o.open(url).read() parsed_html = Parser(url, "text/xml", text) self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'iPad and iPhone Application Development (SD)') self.assertEqual(len(parsed_html.mediaItems), 43) self.checkItems(parsed_html.mediaItems) def testMulticorePage(self): url = "http://itunes.apple.com/us/course/multicore-programming-primer/id495066021" text = self.o.open(url).read() parsed_html = Parser(url, "text/html", text) self.assertEqual(parsed_html.Redirect, '') self.assertEqual(parsed_html.Title, 'Multicore Programming Primer') self.assertEqual(len(parsed_html.mediaItems), 82) for line in parsed_html.mediaItems: self.assertEqual(len(line[4]), 4) # test 4-char extension. def testDumpParsedHTML(self): url = "http://deimos3.apple.com/WebObjects/Core.woa/Browse/georgefox.edu.8155705810.08155705816.8223066656?i=1688428005" text = self.o.open(url).read() parsed_html = Parser(url, "text/HTML", text).HTML file("parsed_test.html", "w").write(parsed_html) if __name__ == "__main__": unittest.main()