googletastic 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +47 -0
- data/Rakefile +103 -0
- data/lib/googletastic/access_rule.rb +45 -0
- data/lib/googletastic/album.rb +22 -0
- data/lib/googletastic/app_engine.rb +103 -0
- data/lib/googletastic/attendee.rb +7 -0
- data/lib/googletastic/base.rb +46 -0
- data/lib/googletastic/calendar.rb +60 -0
- data/lib/googletastic/comment.rb +3 -0
- data/lib/googletastic/document.rb +219 -0
- data/lib/googletastic/event.rb +149 -0
- data/lib/googletastic/ext/file.rb +219 -0
- data/lib/googletastic/ext/pretty_print.xsl +47 -0
- data/lib/googletastic/ext/xml.rb +10 -0
- data/lib/googletastic/ext.rb +1 -0
- data/lib/googletastic/form.rb +131 -0
- data/lib/googletastic/group.rb +44 -0
- data/lib/googletastic/helpers/document.rb +70 -0
- data/lib/googletastic/helpers/event.rb +26 -0
- data/lib/googletastic/helpers/form.rb +102 -0
- data/lib/googletastic/helpers.rb +18 -0
- data/lib/googletastic/image.rb +121 -0
- data/lib/googletastic/mixins/actions.rb +113 -0
- data/lib/googletastic/mixins/attributes.rb +27 -0
- data/lib/googletastic/mixins/finders.rb +78 -0
- data/lib/googletastic/mixins/namespaces.rb +42 -0
- data/lib/googletastic/mixins/parsing.rb +68 -0
- data/lib/googletastic/mixins/requesting.rb +84 -0
- data/lib/googletastic/mixins.rb +5 -0
- data/lib/googletastic/person.rb +41 -0
- data/lib/googletastic/spreadsheet.rb +35 -0
- data/lib/googletastic/sync/document.rb +120 -0
- data/lib/googletastic/sync/form.rb +106 -0
- data/lib/googletastic/sync.rb +46 -0
- data/lib/googletastic/thumbnail.rb +7 -0
- data/lib/googletastic/youtube.rb +25 -0
- data/lib/googletastic.rb +115 -0
- data/spec/benchmarks/document_benchmark.rb +40 -0
- data/spec/config.yml +6 -0
- data/spec/fixtures/data/Doing business in the eMarketPlace.doc +0 -0
- data/spec/fixtures/data/basic.txt +1 -0
- data/spec/fixtures/data/calendar.list.xml +64 -0
- data/spec/fixtures/data/doc_as_html.html +3097 -0
- data/spec/fixtures/data/doc_as_html_html.html +0 -0
- data/spec/fixtures/data/doclist.xml +76 -0
- data/spec/fixtures/data/document.single.xml +30 -0
- data/spec/fixtures/data/end.xml +446 -0
- data/spec/fixtures/data/event.list.xml +62 -0
- data/spec/fixtures/data/form.html +66 -0
- data/spec/fixtures/data/group.list.xml +58 -0
- data/spec/fixtures/data/person.list.xml +53 -0
- data/spec/fixtures/data/photo.list.xml +111 -0
- data/spec/fixtures/data/sample_upload.mp4 +0 -0
- data/spec/fixtures/data/spreadsheet.list.xml +43 -0
- data/spec/fixtures/models/document.rb +7 -0
- data/spec/fixtures/models/event.rb +3 -0
- data/spec/fixtures/models/form.rb +6 -0
- data/spec/fixtures/models/test_model.rb +3 -0
- data/spec/fixtures/results/test.txt +185 -0
- data/spec/googletastic/access_rule_spec.rb +13 -0
- data/spec/googletastic/album_spec.rb +9 -0
- data/spec/googletastic/app_engine_spec.rb +12 -0
- data/spec/googletastic/base_spec.rb +49 -0
- data/spec/googletastic/calendar_spec.rb +11 -0
- data/spec/googletastic/document_spec.rb +163 -0
- data/spec/googletastic/event_spec.rb +34 -0
- data/spec/googletastic/form_spec.rb +43 -0
- data/spec/googletastic/group_spec.rb +9 -0
- data/spec/googletastic/image_spec.rb +9 -0
- data/spec/googletastic/person_spec.rb +9 -0
- data/spec/googletastic/post_spec.rb +46 -0
- data/spec/googletastic/spreadsheet_spec.rb +9 -0
- data/spec/googletastic/youtube_spec.rb +58 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +25 -0
- metadata +187 -0
data/README.textile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
h1. Googletastic
|
2
|
+
|
3
|
+
Making it more than easy to save everything on Google, so all your app has to do is say how to define pretty urls and formatting.
|
4
|
+
|
5
|
+
h2. Installing
|
6
|
+
|
7
|
+
<code>
|
8
|
+
sudo gem install googletastic
|
9
|
+
</code>
|
10
|
+
|
11
|
+
h3. Installing from Source
|
12
|
+
|
13
|
+
<pre><code>
|
14
|
+
git clone git@github.com:viatropos/googletastic.git
|
15
|
+
cd googletastic
|
16
|
+
rake install
|
17
|
+
</code></pre>
|
18
|
+
|
19
|
+
h2. Authentication
|
20
|
+
|
21
|
+
Google's API's have "two types of accounts"http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html#Request:
|
22
|
+
|
23
|
+
# Regular (for individuals): "GOOGLE"
|
24
|
+
# Hosted (for Google Apps): "HOSTED" (or "HOSTED_OR_GOOGLE")
|
25
|
+
|
26
|
+
Google's APIs have "3 types of authentication":http://code.google.com/apis/accounts/index.html:
|
27
|
+
|
28
|
+
# "ClientLogin":http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html: For single users
|
29
|
+
# "OpenAuth":http://code.google.com/apis/accounts/docs/OAuth.html: For "registering an app":http://code.google.com/apis/accounts/docs/RegistrationForWebAppsAuto.html to automatically login. You need to "register your domain here":https://www.google.com/accounts/ManageDomains
|
30
|
+
# "AuthSub":http://code.google.com/apis/accounts/docs/AuthSub.html
|
31
|
+
|
32
|
+
You can permanently unlock the CAPTCHA for a computer by going "here":https://www.google.com/a/accd.org/UnlockCaptcha.
|
33
|
+
|
34
|
+
"Working with Authsub":http://code.google.com/apis/accounts/docs/AuthSub.html#WorkingAuthSub
|
35
|
+
|
36
|
+
If this is for Google Apps, you must go to Google Docs Settings in the Admin Panel, and make sure this is selected: "Users can share documents outside this domain, but will receive a warning each time". The most restrictive, "Users cannot share documents outside this domain", won't allow you to view documents at all. Also, don't select "Require mydomain.com sign-in to view this form." for google forms.
|
37
|
+
|
38
|
+
h2. Top 10 Things I Need the Google Data API to Have
|
39
|
+
|
40
|
+
"Submit Your Ideas to Google!!!":http://productideas.appspot.com/#15/e=220cb&t=2192a&f=22150
|
41
|
+
|
42
|
+
# Partial Responses for everything.
|
43
|
+
So we can get only the data we need. This way we can poll our google docs for any changes and run processes on them.
|
44
|
+
# Publish the docs as plain text.
|
45
|
+
# Textile and Markdown support
|
46
|
+
# Better font support in PDF documents
|
47
|
+
# Everything Should Be TAGGABLE
|
data/Rakefile
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require "rake/rdoctask"
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),'lib','googletastic')
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'spec/rake/spectask'
|
8
|
+
rescue LoadError
|
9
|
+
puts <<-EOS
|
10
|
+
To use rspec for testing you must install rspec gem:
|
11
|
+
gem install rspec
|
12
|
+
EOS
|
13
|
+
exit(0)
|
14
|
+
end
|
15
|
+
|
16
|
+
def files(path, from = __FILE__, &block)
|
17
|
+
Dir.glob(File.join(File.dirname(from), path)) {|file| yield file}
|
18
|
+
end
|
19
|
+
|
20
|
+
APP_ROOT = defined?(RAILS_ROOT) ? RAILS_ROOT : File.dirname(__FILE__)
|
21
|
+
RAILS_ROOT = File.dirname(__FILE__)
|
22
|
+
|
23
|
+
# http://docs.rubygems.org/read/chapter/20
|
24
|
+
spec = Gem::Specification.new do |s|
|
25
|
+
s.name = "googletastic"
|
26
|
+
s.author = "Lance Pollard"
|
27
|
+
s.version = Googletastic::VERSION
|
28
|
+
s.date = "Mon Mar 22 20:12:47 -0700 2010"
|
29
|
+
s.summary = "More than Syncing Rails Apps with the Google Data API"
|
30
|
+
s.homepage = "http://github.com/viatropos/googletastic"
|
31
|
+
s.email = "lancejpollard@gmail.com"
|
32
|
+
s.description = "Googletastic: A New Way of Googling"
|
33
|
+
s.has_rdoc = true
|
34
|
+
s.rubyforge_project = "googletastic"
|
35
|
+
s.platform = Gem::Platform::RUBY
|
36
|
+
s.files = %w(README.textile Rakefile) +
|
37
|
+
Dir["{googletastic,lib,spec}/**/*"] -
|
38
|
+
Dir["spec/tmp"]
|
39
|
+
s.extra_rdoc_files = %w(README.textile)
|
40
|
+
s.require_path = "lib"
|
41
|
+
s.add_dependency("nokogiri")
|
42
|
+
s.add_dependency("activesupport", ">= 2.3.5")
|
43
|
+
s.add_dependency("activerecord", ">= 2.3.5")
|
44
|
+
s.add_dependency("gdata")
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Create .gemspec file (useful for github)"
|
48
|
+
task :gemspec do
|
49
|
+
File.open("#{spec.name}.gemspec", "w") do |f|
|
50
|
+
f.puts spec.to_ruby
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Build the gem into the current directory"
|
55
|
+
task :gem => :gemspec do
|
56
|
+
`gem build #{spec.name}.gemspec`
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Print a list of the files to be put into the gem"
|
60
|
+
task :manifest => :clean do
|
61
|
+
File.open("Manifest", "w") do |f|
|
62
|
+
spec.files.each do |file|
|
63
|
+
f.puts file
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
69
|
+
pkg.gem_spec = spec
|
70
|
+
end
|
71
|
+
|
72
|
+
desc "Install the gem locally"
|
73
|
+
task :install => [:package] do
|
74
|
+
sh %{sudo gem install pkg/googletastic-#{Googletastic::VERSION} --no-ri --no-rdoc}
|
75
|
+
end
|
76
|
+
|
77
|
+
desc "Run all specs"
|
78
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
79
|
+
t.spec_opts = ["--color"]
|
80
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Print specdocs"
|
84
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
85
|
+
t.spec_opts = ["--format", "specdoc"]
|
86
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "Generate the rdoc"
|
90
|
+
Rake::RDocTask.new do |rdoc|
|
91
|
+
files = ["README.textile", "lib/**/*.rb"]
|
92
|
+
rdoc.rdoc_files.add(files)
|
93
|
+
rdoc.main = "README.textile"
|
94
|
+
rdoc.title = "Googletastic: A New Way of Googling"
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "Run the rspec"
|
98
|
+
task :default => :spec
|
99
|
+
|
100
|
+
desc "Run Googletastic Benchmarks"
|
101
|
+
task :benchmark do
|
102
|
+
files("spec/benchmarks/*") {|file| system("ruby #{file}") }
|
103
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Googletastic::AccessRule < Googletastic::Base
|
2
|
+
|
3
|
+
attr_accessor :title, :agent, :role, :email
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def build_url(options)
|
8
|
+
base = nil
|
9
|
+
if options.has_key?(:document_id)
|
10
|
+
options[:client] = Googletastic::Document.client
|
11
|
+
base = "http://docs.google.com/feeds/acl/private/full/#{options[:document_id]}"
|
12
|
+
end
|
13
|
+
options[:url] = base
|
14
|
+
super(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def unmarshall(xml)
|
18
|
+
records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
|
19
|
+
title = id = value = nil # reset
|
20
|
+
id = record.xpath("atom:id", ns_tag("atom")).text
|
21
|
+
title = record.xpath("atom:title", ns_tag("atom")).text
|
22
|
+
scope = record.xpath("gAcl:scope", ns_tag("gAcl")).first
|
23
|
+
role = record.xpath("gAcl:role", ns_tag("gAcl")).first["value"].to_s
|
24
|
+
Googletastic::AccessRule.new(
|
25
|
+
:id => id,
|
26
|
+
:title => title,
|
27
|
+
:email => scope["value"].to_s,
|
28
|
+
:agent => scope["type"].to_s,
|
29
|
+
:role => role
|
30
|
+
)
|
31
|
+
end
|
32
|
+
records
|
33
|
+
end
|
34
|
+
|
35
|
+
def marshall(record)
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_access?(email)
|
42
|
+
self.email == email
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Googletastic::Album < Googletastic::Base
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def client_class
|
6
|
+
"Photos"
|
7
|
+
end
|
8
|
+
|
9
|
+
def index_url
|
10
|
+
"http://picasaweb.google.com/data/feed/api/user/default"
|
11
|
+
end
|
12
|
+
|
13
|
+
def unmarshall(xml)
|
14
|
+
records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
|
15
|
+
|
16
|
+
end
|
17
|
+
records
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module GData
|
2
|
+
module Client
|
3
|
+
|
4
|
+
# Client class to wrap working with the TBD App Engine API.
|
5
|
+
class AppEngine < Base
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
options[:clientlogin_service] = 'ah'
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
# override so we can get cookie
|
13
|
+
def get(url)
|
14
|
+
set_cookie(url)
|
15
|
+
response = self.make_request(:get, url)
|
16
|
+
if response.status_code == 302
|
17
|
+
# if it's redirecting us, we need to get the cookie
|
18
|
+
new_cookie(url)
|
19
|
+
# now we can make the request again
|
20
|
+
response = self.make_request(:get, url)
|
21
|
+
end
|
22
|
+
response
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_cookie(url)
|
26
|
+
cookie = get_cookie(url)
|
27
|
+
headers["Cookie"] = cookie
|
28
|
+
Googletastic.credentials[:cookie] = cookie
|
29
|
+
# save the cookie to a file since we're
|
30
|
+
# not in the browser!
|
31
|
+
storage = Googletastic.keys.has_key?(:storage) ? Googletastic.keys[:storage] : :filesystem
|
32
|
+
|
33
|
+
# heroku can't store anything on filesystem
|
34
|
+
if storage == :filesystem
|
35
|
+
File.open("config/gdata.yml", 'w') do |file|
|
36
|
+
file.write Googletastic.credentials.to_yaml
|
37
|
+
end
|
38
|
+
elsif storage == :environment
|
39
|
+
ENV["GOOGLETASTIC_GAE_COOKIE"] = cookie
|
40
|
+
end
|
41
|
+
cookie
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_cookie(url)
|
45
|
+
uri = URI.parse(url)
|
46
|
+
url_for_cookie = "#{uri.scheme}://#{uri.host}/_ah/login?continue=#{url}&auth=#{auth_handler.token}"
|
47
|
+
response = self.make_request(:get, url_for_cookie)
|
48
|
+
cookie = response.headers["set-cookie"].split('; ',2)[0]
|
49
|
+
cookie
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_cookie(url)
|
53
|
+
if Googletastic.credentials[:cookie]
|
54
|
+
headers["Cookie"] = Googletastic.credentials[:cookie]
|
55
|
+
return headers["Cookie"]
|
56
|
+
else
|
57
|
+
new_cookie(url)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# globally push to google app engine, using the python library
|
62
|
+
# pushes files to gae
|
63
|
+
# required options:
|
64
|
+
# folder => where the python command executes
|
65
|
+
# username, password
|
66
|
+
def push(username, password, options = {})
|
67
|
+
options.symbolize_keys!
|
68
|
+
raise "Please define local GAE Folder" unless options[:folder] and File.exists?(options[:folder])
|
69
|
+
sleep 0.5
|
70
|
+
# update GAE
|
71
|
+
puts "Pushing to GAE..."
|
72
|
+
cmd = "appcfg.py update #{options[:folder]}"
|
73
|
+
original_values = [STDOUT.sync, STDERR.sync, $expect_verbose]
|
74
|
+
STDOUT.sync = true
|
75
|
+
STDERR.sync = true
|
76
|
+
$expect_verbose = true
|
77
|
+
PTY.spawn("#{cmd} 2>&1") do | input, output, pid |
|
78
|
+
begin
|
79
|
+
input.expect("Email:") do
|
80
|
+
output.puts("#{username}\n")
|
81
|
+
end
|
82
|
+
input.expect("Password") do
|
83
|
+
output.write("#{password}\n")
|
84
|
+
end
|
85
|
+
while ((str = input.gets) != nil)
|
86
|
+
puts str
|
87
|
+
end
|
88
|
+
rescue Exception => e
|
89
|
+
puts "GAE Error... #{e.inspect}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
STDOUT.sync = original_values[0]
|
93
|
+
STDERR.sync = original_values[1]
|
94
|
+
$expect_verbose = original_values[2]
|
95
|
+
sleep 0.5
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module Googletastic::AppEngine
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Googletastic::Base < Hash
|
2
|
+
|
3
|
+
include Googletastic::Mixins::Namespaces
|
4
|
+
include Googletastic::Mixins::Attributes
|
5
|
+
include Googletastic::Mixins::Requesting
|
6
|
+
include Googletastic::Mixins::Parsing
|
7
|
+
include Googletastic::Mixins::Finders
|
8
|
+
include Googletastic::Mixins::Actions
|
9
|
+
|
10
|
+
# ID's are specifically the hash key for each entry.
|
11
|
+
# They don't include the url for finding the document,
|
12
|
+
# which can be inferred from the "action" you're taking on it,
|
13
|
+
# and the general api.
|
14
|
+
attr_accessor :id
|
15
|
+
attr_accessor :acl
|
16
|
+
attr_accessor :created_at, :updated_at
|
17
|
+
attr_accessor :attachment_path # for docs, images...
|
18
|
+
# classes/records it is synced with
|
19
|
+
attr_accessor :synced_with
|
20
|
+
attr_accessor :response
|
21
|
+
|
22
|
+
def synced_with
|
23
|
+
@synced_with ||= []
|
24
|
+
@synced_with
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(attributes = {})
|
28
|
+
self.attributes = attributes unless attributes.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self # Class methods
|
32
|
+
|
33
|
+
# This is referring to the gdata gem's implementation,
|
34
|
+
# which more closely maps to the google api
|
35
|
+
# override if you name classes differently.
|
36
|
+
def client_class
|
37
|
+
self.to_s.split("::").last
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_xml
|
43
|
+
self.class.marshall(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Googletastic::Calendar < Googletastic::Base
|
2
|
+
|
3
|
+
attr_accessor :created_at, :updated_at, :title, :role
|
4
|
+
attr_accessor :color, :hidden, :selected, :timezone
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def index_url
|
9
|
+
"http://www.google.com/calendar/feeds/default/allcalendars/full"
|
10
|
+
end
|
11
|
+
|
12
|
+
def show_url(id)
|
13
|
+
"http://www.google.com/calendar/embed?src=#{id}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def unmarshall(xml)
|
17
|
+
records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
|
18
|
+
id = record.xpath("atom:id", ns_tag("atom")).first.text
|
19
|
+
title = record.xpath("atom:title", ns_tag("atom")).first.text
|
20
|
+
created_at = record.xpath("atom:published", ns_tag("atom")).first.text
|
21
|
+
updated_at = record.xpath("atom:updated", ns_tag("atom")).first.text
|
22
|
+
|
23
|
+
role = record.xpath("gCal:accesslevel", ns_tag("gCal")).first["value"]
|
24
|
+
color = record.xpath("gCal:color", ns_tag("gCal")).first["value"]
|
25
|
+
hidden = record.xpath("gCal:hidden", ns_tag("gCal")).first["value"]
|
26
|
+
selected = record.xpath("gCal:selected", ns_tag("gCal")).first["value"]
|
27
|
+
timezone = record.xpath("gCal:timezone", ns_tag("gCal")).first["value"]
|
28
|
+
|
29
|
+
Googletastic::Calendar.new(
|
30
|
+
:id => id,
|
31
|
+
:title => title,
|
32
|
+
:created_at => created_at,
|
33
|
+
:updated_at => updated_at,
|
34
|
+
:role => role,
|
35
|
+
:color => color,
|
36
|
+
:selected => selected,
|
37
|
+
:timezone => timezone
|
38
|
+
)
|
39
|
+
end
|
40
|
+
records
|
41
|
+
end
|
42
|
+
|
43
|
+
def marshall(record)
|
44
|
+
Nokogiri::XML::Builder.new { |xml|
|
45
|
+
xml.entry(ns_xml("atom", "gCal")) {
|
46
|
+
if record.id
|
47
|
+
xml.id {
|
48
|
+
xml.text "#{ID}#{record.id}"
|
49
|
+
}
|
50
|
+
end
|
51
|
+
xml.title {
|
52
|
+
xml.text record.title
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}.to_xml
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# "Anonymous modules have no name to be referenced by"
|
2
|
+
# If you have a class constant that's undefined
|
3
|
+
class Googletastic::Document < Googletastic::Base
|
4
|
+
|
5
|
+
ID = "https://docs.google.com/feeds/default/private/full/"
|
6
|
+
FEED_BASE = 'http://docs.google.com/feeds' unless defined?(FEED_BASE)
|
7
|
+
FEED = 'http://docs.google.com/feeds/documents/private/full' unless defined?(FEED)
|
8
|
+
BASE_URL = "http://docs.google.com" unless defined?(BASE_URL)
|
9
|
+
DOCS_DOWNLOAD = 'download/documents/Export'
|
10
|
+
CSV = "text/csv" unless defined?(CSV)
|
11
|
+
TSV = "text/tab-separated-values" unless defined?(TSV)
|
12
|
+
TAB = "text/tab-separated-values" unless defined?(TAB)
|
13
|
+
HTML = "text/html" unless defined?(HTML)
|
14
|
+
HTM = "text/html" unless defined?(HTM)
|
15
|
+
DOC = "application/msword" unless defined?(DOC)
|
16
|
+
DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" unless defined?(DOCX)
|
17
|
+
ODS = "application/x-vnd.oasis.opendocument.spreadsheet" unless defined?(ODS)
|
18
|
+
ODT = "application/vnd.oasis.opendocument.text" unless defined?(ODT)
|
19
|
+
RTF = "application/rtf" unless defined?(RTF)
|
20
|
+
SXW = "application/vnd.sun.xml.writer" unless defined?(SXW)
|
21
|
+
TXT = "text/plain" unless defined?(TXT)
|
22
|
+
XLS = "application/vnd.ms-excel" unless defined?(XLS)
|
23
|
+
XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" unless defined?(XLSX)
|
24
|
+
PDF = "application/pdf" unless defined?(PDF)
|
25
|
+
PPT = "application/vnd.ms-powerpoint" unless defined?(PPT)
|
26
|
+
PPS = "application/vnd.ms-powerpoint" unless defined?(PPS)
|
27
|
+
|
28
|
+
attr_accessor :title, :kind, :categories, :resource_id, :ext
|
29
|
+
|
30
|
+
def has_access?(email)
|
31
|
+
self.class.first(:url => update_url)
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :body
|
35
|
+
def body
|
36
|
+
return @body if @body
|
37
|
+
@body = Nokogiri::XML(view.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :content
|
41
|
+
# returns the content within the body tags for the document
|
42
|
+
def content
|
43
|
+
return @content if @content
|
44
|
+
body.xpath("//img").each do |img_node|
|
45
|
+
if img_node["src"] =~ /File\?/
|
46
|
+
img_node["src"] = "#{BASE_URL}/#{img_node['src']}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
body.xpath("//div[@id='google-view-footer']").each { |n| n.unlink }
|
50
|
+
@content = body.xpath("//div[@id='doc-contents']").first
|
51
|
+
end
|
52
|
+
|
53
|
+
def acl
|
54
|
+
return @acl if @acl
|
55
|
+
@acl = Googletastic::AccessRule.find(:all, :document_id => id)
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns the styles for the document, in case you need them
|
59
|
+
def styles
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def view
|
64
|
+
client.get(self.show_url)
|
65
|
+
end
|
66
|
+
|
67
|
+
def download(format = "pdf")
|
68
|
+
client.get(self.download_url + "&exportFormat=#{format}")
|
69
|
+
end
|
70
|
+
|
71
|
+
def new_record?
|
72
|
+
return false if !self.id.nil?
|
73
|
+
return self.class.first(:title => self.title, :exact_title => true).nil?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Time.now.xmlschema
|
77
|
+
class << self
|
78
|
+
|
79
|
+
def client_class
|
80
|
+
"DocList"
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid_queries
|
84
|
+
{
|
85
|
+
:title => "title",
|
86
|
+
:exact_title => "title-exact",
|
87
|
+
:opened_after => "opened-min",
|
88
|
+
:opened_before => "opened-max",
|
89
|
+
:edited_after => "edited-min",
|
90
|
+
:edited_before => "edited-max",
|
91
|
+
:owner => "owner",
|
92
|
+
:writer => "writer",
|
93
|
+
:reader => "reader",
|
94
|
+
:show_folders => "showfolders",
|
95
|
+
:show_deleted => "showdeleted",
|
96
|
+
:optical_character_recognition => "ocr",
|
97
|
+
:translate_to => "targetLanguage",
|
98
|
+
:language => "sourceLanguage",
|
99
|
+
:permanent_delete => "delete",
|
100
|
+
:convert => "convert",
|
101
|
+
:format => "exportFormat"
|
102
|
+
}.merge(super)
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid_category?(value)
|
106
|
+
%w(document spreadsheet folder presentation pdf form).include?(value)
|
107
|
+
end
|
108
|
+
|
109
|
+
def index_url(ids = nil)
|
110
|
+
"http://docs.google.com/feeds/documents/private/full"
|
111
|
+
end
|
112
|
+
|
113
|
+
# docid (all lowercase)
|
114
|
+
def show_url(id)
|
115
|
+
"http://docs.google.com/View?docID=#{id}&revision=_latest"
|
116
|
+
end
|
117
|
+
|
118
|
+
def get_url(id)
|
119
|
+
"https://docs.google.com/feeds/default/private/full/#{id}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def edit_url(id)
|
123
|
+
"http://docs.google.com/Doc?docid=#{id}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def update_url(id)
|
127
|
+
if has_attachment?
|
128
|
+
"http://docs.google.com/feeds/media/private/full/#{self.id}"
|
129
|
+
else
|
130
|
+
"http://docs.google.com/feeds/documents/private/full/#{self.id}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# docId (camelcase)
|
135
|
+
def download_url(id)
|
136
|
+
"http://docs.google.com/feeds/download/documents/Export?docId=#{id}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def upload_url(id = nil)
|
140
|
+
index_url(id)
|
141
|
+
end
|
142
|
+
|
143
|
+
%w(document spreadsheet folder presentation pdf form).each do |category|
|
144
|
+
define_method category do
|
145
|
+
all(:url => "#{index_url}/-/#{category}")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def download(*args)
|
150
|
+
old_options = args.extract_options!
|
151
|
+
options = {}
|
152
|
+
options[:format] = old_options[:format] || "pdf"
|
153
|
+
options[:url] = download_url(args.first.is_a?(String) ? args.first : old_options[:id])
|
154
|
+
args << options
|
155
|
+
find(*args)
|
156
|
+
end
|
157
|
+
|
158
|
+
def upload(*args)
|
159
|
+
client.post_file(upload_url, args.first, TXT)
|
160
|
+
end
|
161
|
+
|
162
|
+
def build_url(options)
|
163
|
+
base = options.has_key?(:url) ? options[:url] : self.index_url
|
164
|
+
base.gsub!(/full$/, "expandAcl") if options.has_key?(:include) and options[:include].index(:acl)
|
165
|
+
base << "/#{options[:id]}" if options.has_key?(:id)
|
166
|
+
options[:url] = base
|
167
|
+
super(options)
|
168
|
+
end
|
169
|
+
|
170
|
+
def unmarshall(xml)
|
171
|
+
records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
|
172
|
+
kind = title = id = categories = value = nil # reset
|
173
|
+
title = record.xpath("atom:title", ns_tag("atom")).text
|
174
|
+
categories = record.xpath("atom:category", ns_tag("atom")).collect do |category|
|
175
|
+
value = category["label"].to_s
|
176
|
+
kind = value if !kind and valid_category?(value)
|
177
|
+
value
|
178
|
+
end
|
179
|
+
resource_id = record.xpath("gd:resourceId", ns_tag("gd")).text
|
180
|
+
id = resource_id.gsub(/#{kind}:/, "")
|
181
|
+
created_at = record.xpath("atom:published", ns_tag("atom")).text
|
182
|
+
updated_at = record.xpath("atom:updated", ns_tag("atom")).text
|
183
|
+
puts "UPDATED AT: #{updated_at.inspect}"
|
184
|
+
Googletastic::Document.new(
|
185
|
+
:id => id,
|
186
|
+
:title => title,
|
187
|
+
:categories => categories,
|
188
|
+
:kind => kind,
|
189
|
+
:resource_id => resource_id,
|
190
|
+
:ext => File.extname(title),
|
191
|
+
:created_at => DateTime.parse(created_at),
|
192
|
+
:updated_at => DateTime.parse(updated_at)
|
193
|
+
)
|
194
|
+
end
|
195
|
+
records
|
196
|
+
end
|
197
|
+
|
198
|
+
def marshall(record)
|
199
|
+
Nokogiri::XML::Builder.new { |xml|
|
200
|
+
xml.entry(ns_xml("atom", "exif", "openSearch")) {
|
201
|
+
if record.id
|
202
|
+
xml.id_ {
|
203
|
+
xml.text get_url(record.resource_id)
|
204
|
+
}
|
205
|
+
end
|
206
|
+
record.categories.each do |category|
|
207
|
+
xml.category(
|
208
|
+
:scheme => "http://schemas.google.com/g/2005#kind",
|
209
|
+
:term => "#{category}"
|
210
|
+
)
|
211
|
+
end
|
212
|
+
xml.title {
|
213
|
+
xml.text record.title
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}.to_xml
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|