googletastic 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. data/README.textile +47 -0
  2. data/Rakefile +103 -0
  3. data/lib/googletastic/access_rule.rb +45 -0
  4. data/lib/googletastic/album.rb +22 -0
  5. data/lib/googletastic/app_engine.rb +103 -0
  6. data/lib/googletastic/attendee.rb +7 -0
  7. data/lib/googletastic/base.rb +46 -0
  8. data/lib/googletastic/calendar.rb +60 -0
  9. data/lib/googletastic/comment.rb +3 -0
  10. data/lib/googletastic/document.rb +219 -0
  11. data/lib/googletastic/event.rb +149 -0
  12. data/lib/googletastic/ext/file.rb +219 -0
  13. data/lib/googletastic/ext/pretty_print.xsl +47 -0
  14. data/lib/googletastic/ext/xml.rb +10 -0
  15. data/lib/googletastic/ext.rb +1 -0
  16. data/lib/googletastic/form.rb +131 -0
  17. data/lib/googletastic/group.rb +44 -0
  18. data/lib/googletastic/helpers/document.rb +70 -0
  19. data/lib/googletastic/helpers/event.rb +26 -0
  20. data/lib/googletastic/helpers/form.rb +102 -0
  21. data/lib/googletastic/helpers.rb +18 -0
  22. data/lib/googletastic/image.rb +121 -0
  23. data/lib/googletastic/mixins/actions.rb +113 -0
  24. data/lib/googletastic/mixins/attributes.rb +27 -0
  25. data/lib/googletastic/mixins/finders.rb +78 -0
  26. data/lib/googletastic/mixins/namespaces.rb +42 -0
  27. data/lib/googletastic/mixins/parsing.rb +68 -0
  28. data/lib/googletastic/mixins/requesting.rb +84 -0
  29. data/lib/googletastic/mixins.rb +5 -0
  30. data/lib/googletastic/person.rb +41 -0
  31. data/lib/googletastic/spreadsheet.rb +35 -0
  32. data/lib/googletastic/sync/document.rb +120 -0
  33. data/lib/googletastic/sync/form.rb +106 -0
  34. data/lib/googletastic/sync.rb +46 -0
  35. data/lib/googletastic/thumbnail.rb +7 -0
  36. data/lib/googletastic/youtube.rb +25 -0
  37. data/lib/googletastic.rb +115 -0
  38. data/spec/benchmarks/document_benchmark.rb +40 -0
  39. data/spec/config.yml +6 -0
  40. data/spec/fixtures/data/Doing business in the eMarketPlace.doc +0 -0
  41. data/spec/fixtures/data/basic.txt +1 -0
  42. data/spec/fixtures/data/calendar.list.xml +64 -0
  43. data/spec/fixtures/data/doc_as_html.html +3097 -0
  44. data/spec/fixtures/data/doc_as_html_html.html +0 -0
  45. data/spec/fixtures/data/doclist.xml +76 -0
  46. data/spec/fixtures/data/document.single.xml +30 -0
  47. data/spec/fixtures/data/end.xml +446 -0
  48. data/spec/fixtures/data/event.list.xml +62 -0
  49. data/spec/fixtures/data/form.html +66 -0
  50. data/spec/fixtures/data/group.list.xml +58 -0
  51. data/spec/fixtures/data/person.list.xml +53 -0
  52. data/spec/fixtures/data/photo.list.xml +111 -0
  53. data/spec/fixtures/data/sample_upload.mp4 +0 -0
  54. data/spec/fixtures/data/spreadsheet.list.xml +43 -0
  55. data/spec/fixtures/models/document.rb +7 -0
  56. data/spec/fixtures/models/event.rb +3 -0
  57. data/spec/fixtures/models/form.rb +6 -0
  58. data/spec/fixtures/models/test_model.rb +3 -0
  59. data/spec/fixtures/results/test.txt +185 -0
  60. data/spec/googletastic/access_rule_spec.rb +13 -0
  61. data/spec/googletastic/album_spec.rb +9 -0
  62. data/spec/googletastic/app_engine_spec.rb +12 -0
  63. data/spec/googletastic/base_spec.rb +49 -0
  64. data/spec/googletastic/calendar_spec.rb +11 -0
  65. data/spec/googletastic/document_spec.rb +163 -0
  66. data/spec/googletastic/event_spec.rb +34 -0
  67. data/spec/googletastic/form_spec.rb +43 -0
  68. data/spec/googletastic/group_spec.rb +9 -0
  69. data/spec/googletastic/image_spec.rb +9 -0
  70. data/spec/googletastic/person_spec.rb +9 -0
  71. data/spec/googletastic/post_spec.rb +46 -0
  72. data/spec/googletastic/spreadsheet_spec.rb +9 -0
  73. data/spec/googletastic/youtube_spec.rb +58 -0
  74. data/spec/spec.opts +1 -0
  75. data/spec/spec_helper.rb +25 -0
  76. 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,7 @@
1
+ class Googletastic::Attendee < Hash # simple value object
2
+
3
+ include Googletastic::Mixins::Attributes
4
+
5
+ attr_accessor :email, :role, :name
6
+
7
+ 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,3 @@
1
+ class Googletastic::Comment < Googletastic::Base
2
+
3
+ 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