Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
bemurphy committed Dec 17, 2011
0 parents commit 9ca12d0
Show file tree
Hide file tree
Showing 23 changed files with 568 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
*.gem
.bundle
Gemfile.lock
pkg/*
.rvmrc
4 changes: 4 additions & 0 deletions Gemfile
@@ -0,0 +1,4 @@
source "http://rubygems.org"

# Specify your gem's dependencies in das_catalog.gemspec
gemspec
7 changes: 7 additions & 0 deletions LICENSE
@@ -0,0 +1,7 @@
Copyright (c) 2011 Brendon Murphy

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
65 changes: 65 additions & 0 deletions README.md
@@ -0,0 +1,65 @@
Das Catalog
===========

The Das Catalog gem - used for downloading screencasts from the [Destroy All Software screencasts catalog](https://www.destroyallsoftware.com/screencasts). Uses the rss feed plus Mechanize to log you in and then download unsaved movies to a local directory.

This gem is in not linked to Destroy All Software or endorsed by Gary Bernhardt. I just like his stuff.

Requirements
------------

I've written and only run the gem on Ruby 1.9.2. YMMV, but 1.8.x will definitely blow up.

The gem also expects that wget exists in your path, as it uses it for downloaded the movie file.

Installation
------------

Install the gem

$ gem install das_catalog

Usage
-----

Run from the command line:

$ das_catalog_sync

It will prompt your for some settings (username, password, storage directory) and then be off and running.

Stored Configuration
--------------------

If you don't provide configuration, `das_catalog_sync` will prompt you for configuration values when you run it.

However, you can persist a full or partial config in ~/.das_catalog.yml as such:

---
username: your_username
password: your_password
downloads_directory: /Users/programmer/Movies/DAS

If you wish to exclude any config, leave it out and `das_catalog_sync` will prompt your for it at runtime.

Basic Process
-------------

The steps by which the gem works are fairly simple:

1. Fetch the rss feed to see what is in the catalog
2. Use Mechanize to log in at the DAS sign in page
3. Find links in the feed that have not been downloaded yet
4. Construct a download link, and grab the location header from the redirect (the movie on S3)
5. Offload the download to wget for efficient processing

TODO
----
* Finish missing spec coverage (mostly in DasCatalog toplevel namespace)
* Hook up Aruba for bin testing
* Switch bin to [GLI](https://github.com/davetron5000/gli)?
* CLI interface for tracking if you've watched a specific screencast
* Make missing directory automatically
* better wget detection/warnings
* Lighten some dependencies (feed fetching, highline, etc)
* Better cli access to modify the underlying pstore
9 changes: 9 additions & 0 deletions Rakefile
@@ -0,0 +1,9 @@
require 'bundler'
Bundler::GemHelper.install_tasks

require 'rake/testtask'

Rake::TestTask.new do |t|
t.pattern = "spec/*_spec.rb"
end

24 changes: 24 additions & 0 deletions bin/das_catalog_sync
@@ -0,0 +1,24 @@
#!/usr/bin/env ruby

require 'yaml'
require 'highline/import'

$LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__))

require 'das_catalog'

config_file = File.expand_path("~/.das_catalog.yml")

config = if File.exists?(config_file)
YAML.load_file(config_file)
else
{}
end

DasCatalog.configure do |c|
c.username = config["username"] || ask("Enter your DAS username: ") { |q| q.echo = true }
c.password = config["password"] || ask("Enter your DAS password: ") { |q| q.echo = "*" }
c.downloads_directory = config["downloads_directory"] || ask("Enter your DAS downloads directory: ") { |q| q.echo = "*" }
end

DasCatalog.sync
27 changes: 27 additions & 0 deletions das_catalog.gemspec
@@ -0,0 +1,27 @@
# -*- encoding: utf-8 -*-
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
require "das_catalog/version"

Gem::Specification.new do |s|
s.name = 'das_downloader'
s.version = '0.0.1'
s.platform = Gem::Platform::RUBY
s.author = 'Brendon Murphy'
s.email = 'xternal1+github@gmail.com'
s.summary = 'Download screencasts from Destroy All Software catalog'
s.description = 'Download screencasts from Destroy All Software catalog. Uses the rss feed plus Mechanize to log you in and then download new movies to your local drive.'

s.files = Dir['lib/**/*.rb'] + Dir['bin/*'] + %w(README.md LICENSE)
s.test_files = Dir['spec/**/*.rb']
s.require_path = 'lib'

s.executables = ["das_catalog_sync"]

s.add_dependency('feedzirra')
s.add_dependency('mechanize')
s.add_dependency('highline')

s.add_development_dependency('minitest')
s.add_development_dependency('mocha')
s.add_development_dependency('fakefs')
end
60 changes: 60 additions & 0 deletions lib/das_catalog.rb
@@ -0,0 +1,60 @@
require 'feedzirra'
require 'mechanize'
require 'shellwords'
require 'uri'
require 'pstore'
require 'forwardable'

module DasCatalog
SIGN_IN_URL = "https://www.destroyallsoftware.com/screencasts/users/sign_in"

# TODO spec this
class << self
attr_accessor :username
attr_accessor :password
attr_accessor :downloads_directory

attr_writer :logger
def logger
@logger ||= StdLogger.new
end

attr_writer :tracker_file
def tracker_file
@tracker_file ||= File.expand_path("~/.das_tracker.pstore")
end
end

def self.configure
yield self
end

# TODO spec this
def self.agent
@agent ||= begin
agent = Mechanize.new
page = agent.get SIGN_IN_URL
form = page.forms.first
form['user[email]'] = username
form['user[password]'] = password
agent.submit form
agent
end
end

# TODO spec this
def self.sync
feed = Feed.get
feed.entries.each do |entry|
sc = Screencast.for_link(entry.url)
sc.download
end
end
end

require_relative "das_catalog/downloader"
require_relative "das_catalog/feed"
require_relative "das_catalog/std_logger"
require_relative "das_catalog/screencast"
require_relative "das_catalog/screencast_data"
require_relative "das_catalog/store"
16 changes: 16 additions & 0 deletions lib/das_catalog/downloader.rb
@@ -0,0 +1,16 @@
module DasCatalog
# TODO spec this
class Downloader
def self.process(screencast)
# Mechanize was choking on large file downloads and keeps them in memory,
# outsource to wget
agent = DasCatalog.agent
agent.redirect_ok = false
result = agent.get screencast.download_link
url = result.header["location"]
url =~ /^http.+?([^\/]+\.mov)\?/
output_filename = $1
`wget --quiet #{Shellwords.escape url} -O #{DasCatalog.downloads_directory}/#{output_filename}`
end
end
end
9 changes: 9 additions & 0 deletions lib/das_catalog/feed.rb
@@ -0,0 +1,9 @@
module DasCatalog
class Feed
FEED_URL = "https://www.destroyallsoftware.com/screencasts/feed"

def self.get
Feedzirra::Feed.fetch_and_parse(FEED_URL)
end
end
end
30 changes: 30 additions & 0 deletions lib/das_catalog/screencast.rb
@@ -0,0 +1,30 @@
module DasCatalog
class Screencast
extend Forwardable
include LoggerAccess

attr_reader :screencast_data
def_delegators :@screencast_data, :watched?, :downloaded?, :download_link

def initialize(screencast_data)
@screencast_data = screencast_data
end

def self.for_link(link)
new ScreencastData.for_link(link)
end

def to_s
screencast_data.link
end

def download
return false if screencast_data.downloaded?
log.info "Starting download of #{self}"
Downloader.process(self)
screencast_data.downloaded
log.info "Finished download of #{self}"
screencast_data.save
end
end
end
43 changes: 43 additions & 0 deletions lib/das_catalog/screencast_data.rb
@@ -0,0 +1,43 @@
module DasCatalog
class ScreencastData
attr_reader :link, :watched, :downloaded

def initialize(link)
@link = link
end

def self.for_link(link)
Store.find(link) or new(link) #Store.store(new(link))
end

alias :id :link

def download_link
"#{link}/download"
end

def downloaded
@downloaded = true
end

def downloaded?
@downloaded
end

def watched
@watched = true
end

def unwatched
@watched = false
end

def watched?
@watched
end

def save
Store.store(self)
end
end
end
21 changes: 21 additions & 0 deletions lib/das_catalog/std_logger.rb
@@ -0,0 +1,21 @@
require 'logger'
require 'delegate'

module DasCatalog
class StdLogger < DelegateClass(::Logger)
def initialize
@logger = ::Logger.new(STDOUT)
super(@logger)
end
end

module LoggerAccess
def self.included(klass)
klass.extend(self)
end

def log
DasCatalog.logger
end
end
end
17 changes: 17 additions & 0 deletions lib/das_catalog/store.rb
@@ -0,0 +1,17 @@
module DasCatalog
class Store
def self.find(id)
store = PStore.new(DasCatalog.tracker_file)
store.transaction(true) do
store[id]
end
end

def self.store(screencast_data)
store = PStore.new(DasCatalog.tracker_file)
store.transaction do
store[screencast_data.id] = screencast_data
end
end
end
end
3 changes: 3 additions & 0 deletions lib/das_catalog/version.rb
@@ -0,0 +1,3 @@
module DasCatalog
VERSION = "0.0.1"
end
7 changes: 7 additions & 0 deletions spec/das_catalog_spec.rb
@@ -0,0 +1,7 @@
require_relative "spec_helper"

describe DasCatalog do
it "has a DasCatalog::StdLogger by default" do
assert DasCatalog.logger
end
end
6 changes: 6 additions & 0 deletions spec/das_downloader_spec.rb
@@ -0,0 +1,6 @@
require_relative "spec_helper"

describe DasCatalog::Downloader do
# TODO
end

8 changes: 8 additions & 0 deletions spec/das_feed_spec.rb
@@ -0,0 +1,8 @@
require_relative "spec_helper"

describe DasCatalog::Feed do
it "grabs the feed with feedzirra" do
Feedzirra::Feed.expects(:fetch_and_parse).with("https://www.destroyallsoftware.com/screencasts/feed")
DasCatalog::Feed.get
end
end

0 comments on commit 9ca12d0

Please sign in to comment.