1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env ruby

MY_TASKO_ID = 1        # Your Tasko ID
MY_TASKO_KEY = 'AAAA'  # Your Tasko key
MY_TASKS_DIR = ENV['HOME'] + '/Tasks'
MY_TASKO_SUMMARY_FILE = ENV['HOME'] + '/.tasko.yml'

require 'yaml'
require 'digest/md5'
require 'xmlrpc/client'

class String
  def md5
    Digest::MD5::hexdigest(self)
  end
end

module XMLRPC::ParseContentType
  def parse_content_type(str)
    ["text/xml", "utf-8"]
  end
end

class TaskoAPI
  XMLRPC::Config.module_eval { remove_const(:ENABLE_NIL_PARSER) }
  XMLRPC::Config.const_set(:ENABLE_NIL_PARSER, true)

  URL = "http://taskodone.com/api"

  def initialize(id, pass)
    @id, @pass = id, pass
  end
  
  def newpaper(*args)
    client.call('new', @id, @pass, *args)
  end
  
  def method_missing(method, *args)
    client.call(method, @id, @pass, *args)
  end
  
  private  
  def client
    XMLRPC::Client.new2(URL)
  end
end

class TaskoSyncer
  EXT = '.taskpaper'
  
  def initialize(id, pass, dir, summary_file)
    @tasko = TaskoAPI.new(id, pass)
    @dir = dir
    @summary_file = summary_file
    @summary = YAML::load(File.read(@summary_file)) rescue {}
  end
    
  def sync
    begin
      papers = @tasko.papers

      local_files.each do |file|
        papers = update_file_contents(file, papers)
      end

      papers.each do |paper|
        update_missing_paper paper
      end

      save_summary
    rescue SocketError
      puts "No internet connection"
    end    
  end
  
  private
  
  def write_task_file(paper, data)
    File.open(File.join(@dir, paper + '.taskpaper'), 'w') do |f|
      f.write data
    end
    update_summary(paper, data)
  end
  
  def update_summary(paper, data)
    @summary[paper] = data.md5
  end
  
  def save_summary
    File.open(@summary_file, 'w') do |f|
      f.write @summary.to_yaml
    end
  end
  
  def local_files
    Dir[File.join(@dir, "*#{EXT}")]
  end
  
  def summary_match?(paper, data)
    !@summary[paper].nil? and @summary[paper] == data.md5
  end
  
  def update_file_contents(file, papers)
    paper = File.basename(file, EXT)
    data = File.read(file)

    if summary_match?(paper, data)
      remote_data = @tasko.paper(paper)
      if data != remote_data
        puts "Pulling #{paper}"
        write_task_file(paper, remote_data)
      end
      papers.delete(paper)          
    else
      puts "Pushing #{paper}"

      if papers.delete(paper)
        @tasko.edit(paper, data)
      else
        @tasko.newpaper(paper, data)
      end
      
      update_summary(paper, data)
    end
    
    papers
  end
  
  def update_missing_paper(paper)
    if @summary[paper].nil?
      puts "Adding #{paper}"
      data = @tasko.paper(paper)
      write_task_file(paper, data)
    else
      puts "Deleting #{paper}"
      @tasko.delete(paper)
      @summary.delete(paper)
    end
  end
end

if __FILE__ == $0
  TaskoSyncer.new(MY_TASKO_ID, MY_TASKO_KEY, MY_TASKS_DIR, MY_TASKO_SUMMARY_FILE).sync
end