-
-
Appington concept
Appington. Your applications brought to you.
Appington is, fundamentally, a single-application VNC client with a simple interface for task switching. Where most VNC applications present the user with the entire screen, Appington only shows a single window at any one time. This simplified interface makes interaction easier and saves on client application memory and reduces data transfer allowing the viewer to be more responsive. In some applications, this data transfer saving may be used to facilitate audio capture and transmission.
Applications list
This screen shows a list of all available applications grouped by first letter. In the lower-left, the user can toggle between listing all applications or only listing currently running applications. The right-hand panel shows more information about the selected application. In this example, Google Chrome is selected and running. The current memory and CPU usage are shown along with a note of how many active windows the application has. Because Chrome is currently running, the option to quit is shown. If we had selected an unlaunched application, this button would show the option to launch. In case of emergencies, there is always the option to Force Quit a running application.
Application window (portrait)
This shows a standard single application window. The button in the top left would return the user to the previous screen. From the right, the remaining buttons allow the user to capture a screen shot, maximize the application window to match the current iPad viewport (if possible), refresh the current screen (in case of render errors) and access the application's menu. In OS X, menu access would be accomplished by way of the accessibility API. At the moment, I'm not sure how it would work on other OSs.
Application window (landscape)
This shows a single window of an application with multiple windows. You'll notice the extra button at the top between Menu and Refresh. This menu will allow you to select which window you want to access between however many the application currently has open.
Other images
The partner application to this is a modified VNC server running on the host machine. It is responsible for window management, task-switching, menu parsing and audio capture (Ã la SoundFlower). If there is already a VNC server running, the partner app will leave the standard VNC functionality alone and act purely as a helper, providing the extra functionality but not using extra memory by duplicating functionality. This is a variation of noVNC using the python proxy to manage the socket connection allowing the client to be built in PhoneGap using HTML 5.
Like I said at the top, this hasn't been built yet. It'd be cool if someone did build it, though.
-
Bad tweet, go to your room
I don't tend to say much on the subject of Twitter. I also don't tend to say that much on Twitter itself. That said, I do spend a lot of time on the Internet so here are some things you should stop doing now. Like, right now. Call it ‘Social media bad practice’ if you will, or ‘Tips and Tricks for a Tidier Tweet’ if that's the kind of thing you're into. Whatever, stop doing these:
Autotweeting from 3rd party apps
When you use one of the many apps that track your weight, book-reading habits, location, tweetability or shoe-size, disable the 'post to Twitter?' option, please. When I see these posts, all I read is either:
“According to amIanEejit.com, I'm an Eejit. Are you? Try it now.”
or
“I need public validation. Did I do good? Did I?”
Tweeting a shortened URL which performs an action on the logged-in user's account
Of course, fault for this should also be spread equally between the tweeter, the website which allows GET operations to modify data and the user who isn't wary enough of shortened URLs to expand them first. The person who creates the shortened URL without being aware of the consequences is to blame and an eejit. If you're not sure what I'm talking about, here's an example:
- You have an account on website X which you are logged into
- I have an account on website X which I am logged into
- Website X allows you to delete your account by going to www.example.com/deletemyaccount.php
- You copy that URL and shorten it using bit.ly
- You tweet “Hey, this is what I think of Website X: http://bit.ly/madeupthing”
- I click the link
- My account on Website X gets deleted
- I stab you with pencils.
Using inappropriate hashtags to piggy-back on an unrelated discussion
Some people use #hashtags as tweet meta data providing an extra piece of context on the tweet – “Om nom nom #fridaymorningbaconroll” – while others use them to create fluid, transient chatrooms. Where in the past you'd have used IRC and created a relevant room, using Twitter and a hashtag, you can jump into a conversation and out again without even trying. If you attempt to barge your way in with irrelevant comments, advertising nonsense or general eejicy, you act no better than an out-and-out-spammer and I don't follow no spammers.
Flooding followers with real-time reporting
Use a separate account for this kind of thing. Macrumors do this whenever there's one of those big Steve Jobs parties – if you want to follow all the info, follow the @macrumorslive account. Similarly, Fridaymix do the same thing. Discussions happen with the @fridaymix account while the announcements of what is currently being played come from @fridaymixdj.
Related: Retweeting your other account.
If I wanted to follow the other account, I'd follow the other account.
Also related: Retweeting your own main account
I heard you. Don't be the guy at the party with one punchilne that you tell again and again. I already know that guy. I don't follow him on Twitter.
Posting quotes from conference presentations without context or grammar
This doubly applies if the quote sounds like a half-hearted Zen koan.
“Listen to the youth. They have younger voices.”
“Use torches to light the way. Technology is your torch.”
This triply applies if you use antimetabole
“Don't follow the herd, herd the followers.”
“Don't live beyond means, have meaning beyond living.”
Of course, the worst is probably tweeting about your own blog post in which you discuss tweeting as if it actually matters.
-
...and a salesman, too.
It seems to be a fundamental aspect of the world that, whatever you do for a living, you have to do that and be a salesman. When I say selling, I don't mean the purely business related contract-signing, accounting and banking aspect of sales, I mean really 'Selling yourself'. Marketing, if you will. The bit of the process that involves firm handshakes, giving presentations at conferences, reminding people at every opportunity that you are selling something they need. Even if they don't know they need it. Even if they don't need it.
You could be the greatest web developer known to the history of the interweb creating progressively-enhanced, backwards-compatible masterpieces of semantic mark-up which not only push the boundaries in the latest Webkit nightlies but still fly pixel-perfect in IE6 and you still wouldn't be able to run your own agency without selling your services.
Your iPhone app might be 'The Greatest App Ever Invented' combining the power of Google, the ease of use of Twitter and the graphics of Epic Citadel. It might prove the Riemann Hypothesis, remind you of birthdays, cure cancer all while showing pictures of cats falling asleep but unless somebody actually knows it exists, it's no more useful than those apps that play the noises of bodily functions while simultanesouly being less succesful. By putting it in the iTunes Store you are technically selling it but you're not 'selling it'.
The same situation applies in every industry – writing books, making sandwiches, playing piano, juggling. Unless you are lucky enough to be 'discovered' by someone with the ability to sell but without anything to actually sell, there is no difference between you and everybody else in your field. Despite what you may have learnt in school, you do not get to the top of the class by being the smartest. You get to the top by putting your hand up when the teacher asks a question.
A few months back I saw an article entitled 'Talent does not go unrewarded'. I've seen too many shy, socially awkward developers who won't progress past the minimum acceptable salary for their job title to believe this. More accurately, I'd say 'Talent does not go unrecognised'. They don't get rewarded for their technical wizardry, they get rewarded for convincing their bosses they're worth more than they're currently being paid. For selling themselves.
Evan Williams' recent step down from CEO of Twitter to focus on product develpment strikes me as the developer's ideal – all the success and reward (financial and kudos) without the daily requirement to constantly sell. Of course, Twitter wouldn't have gotten to where it is if he hadn't been able to take on that role along the way.
-
Writing a Plex Plugin Part III
Right.
We've done the required built-in functionality (preference management, for instance) and the bits that talk to Transmission itself. Basically, we're done. Anything else added here is just extra. That is, of course, the best reason to add stuff here. As I have previously ranted at length, there's no point doing anything if you aren't trying to do it as well as it possibly can be done. In this particular instance, that manifests itself in the ability to browse, search for and download torrents all within the Plex client interface.
EZTV
I love EZTV. It makes things easy. Previous versions of this plugin included an EZTV search but after rooting around in the source of the µTorrent plugin, I found some nifty code which turned me onto the clever XML parsing Plex can do.
This function grabs the H2s from the page http://ezrss.it/shows/. If you go there, you'll see that the page lists every TV show in EZTV. The original µTorrent function listed everything but there are a lot of shows there now so it was actually taking a long time just to get that list. As they've split the page up by section, we can just grab the bits we want. This is going to be a full page in Plex (not a popup) so we're using a MediaContainer.
def TVShowListFolders(sender): dir = MediaContainer()
Using the built-in XML module, we can simply pass in a URL and get back an object containing the hierarchical structure of the entire page. Seriously, how simple is this? As it's HTML, add in the option
isHTML=True
.showsPage = XML.ElementFromURL( 'http://ezrss.it/shows/', isHTML=True, errors='ignore' )
Now that we have the whole page structure, take the chunks of the page we want. All the sections we want (and one we don't) are divs with the class 'block' so use that in xpath to pull them out.
blocks = showsPage.xpath('//div[@class="block"]')
The first block is the one we don't want (if you look at the page, it's the one that lists all the letters) so we remove it.
blocks.pop(0)
For each of the remaining blocks, find the text in the first H2. That is the letter title of the section ('A', 'B', 'C', etc). Add that to Plex as a menu item then return the entire list.
for block in blocks: letter = block.xpath("h2")[0].text dir.Append( Function( DirectoryItem( TVShowListSubfolders, letter, subtitle=None, summary=None, thumb=R(ICON), art=R(ART) ), letter=letter ) ) return dir
I hope I'm not the only one impressed with that (although I have a feeling I might be). Using just a couple of lines from the XML module and a sprinkle of xpath and we've got another menu, dynamically generated from a third-party website. If EZTV ever change their layout, it should be a simple matter of changing the xpath to match and we're done. Again.
We can now do the same again but this time, we only pull out a single section based on the letter passed in.
def TVShowListSubfolders(sender, letter): dir = MediaContainer() showsPage = XML.ElementFromURL( 'http://ezrss.it/shows/', isHTML=True, errors='ignore' ) blocks = showsPage.xpath( '//div[@class="block" and h2 = "%s"]' % letter )
Remembering to ignore any 'back to top' links, write out a list of the shows in this section. These will call the TVEpisodeList method next.
for block in blocks: for href in block.xpath('.//a'): if href.text != "# Top": requestUrl = "http://ezrss.it" + href.get("href") + "&mode=rss" dir.Append( Function( DirectoryItem( TVEpisodeList, href.text, subtitle=None, summary=None, thumb=R(ICON), art=R(ART) ), name=href.text, url=requestUrl ) ) return dir
This lists all available torrents for the chosen show. By this point, you should be familiar with how this works. We're using the XML module to grab the page at the URL (this time it's an RSS feed so we don't need to parse it as HTML); we use XPath to iterate through the items in the feed; we generate a menu item from the data which will call a function when selected; we append that to a MediaContainer then return the whole thing to Plex. Done. The AddTorrent function was defined higher up.
def TVEpisodeList(sender, name, url): dir = MediaContainer() feed = XML.ElementFromURL(url, isHTML=False, errors='ignore').xpath("//item") for element in feed: title = element.xpath("title")[0].text link = element.xpath("link")[0].text dir.Append( Function( DirectoryItem( AddTorrent, title, subtitle=None, summary=None, thumb=R(ICON), art=R(ART) ), torrentUrl=link ) ) return dir
Adult considerations...
There is currently a section in the plugin which will allow you to search IsoHunt. This might get dropped in future versions of the plugin as results from IsoHunt are almost exclusively...ahem...adult, regardless of search terms. Sure, that might be exactly what you were looking for but if you were actually looking for Desperate Housewives, you might be surprised when your file comes down and it's actual 'desperate housewives'...
Search EZTV
The final part is a straightforward search of EZTV. The interesting thing to note is that this uses a different type of menu item. Where normally, you'd use a DirectoryItem in a Function, this uses an InputDirectoryItem in a Function. This type of menu item will pop open an on-screen keyboard before calling the target function giving you the opportunity to grab some user input.
It's appended to the menu in the usual way:
dir.Append( Function( InputDirectoryItem( SearchEZTV, L('MenuSearchTV'), "Search the TV shows directory", summary="This will use EZTV to search.", thumb=R(SEARCH), art=R(ART) ) ) )
When the user has entered their input and submitted, the named Function
SearchEZTV
is called with the standard argumentsender
and the extra argumentquery
containing the user's input.This function was a lot longer in the previous version of the Framework. It was so much simpler this time round.
def SearchEZTV(sender, query=None): dir = MediaContainer() url = "http://ezrss.it/search/index.php?simple&mode=rss&show_name=" if query != None: url += "%s" % query feed = XML.ElementFromURL( url, isHTML=False, errors='ignore' ).xpath("//item") if feed == None: return MessageContainer("Error", "Search failed") if len(feed) == 0: return MessageContainer("Error", "No results") for element in feed: title = element.xpath("title")[0].text category = element.xpath("category")[0].text link = element.find("enclosure").get("url") size = prettysize(int(element.find("enclosure").get("length"))) dir.Append( Function( DirectoryItem( AddTorrent, title, subtitle=None, summary="Category: %s\nSize: %s" % (category,size), thumb=R(ICON), art=R(ART) ), torrentUrl=link ) ) return dir
Done
That's it. The only other little thing to mention is how handy it is to use the built-in Log function. The first argument is a standard Python string, the second is 'Should this only turn up in the console when in debug mode?' to which the answer will almost always be 'True'. There is a third argument but unless you're messing with character encodings, you don't need to worry about it.
Log("Message to log is: %s %d" % (errorString, errorCode), True)
Go, make...
If you made it to the end here, you're probably either keen to start making your own Plex plugins or my wife who I am going to get to proofread this. Assuming you're the former, here are some handy links:
Online plugin development manual
There are plenty of bits missing but it's still the best reference available for the framework.
Plex forums
Particularly the Media Server Plugins forum
Plex Plugins Lighthouse
This is where bugs are filed, suggestions made and final plugin submission happens. It's handy for picking little tips if someone else has had the same problem as you.