#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'
require 'net/sftp'
module BashNotes; module MobileNotes
class Sync
def self.process!
BashNote.notes_dir = File.join(ENV['HOME'], '.notes')
delta = {
:new => {:local => [], :remote => []},
:changed => {:local => [], :remote => []}
}
local_notes = BashNote.all
remote_notes = IphodNote.all
# Find notes that have been added locally since last sync.
rn_titles = remote_notes.collect {|n| n.title}
delta[:new][:local] = local_notes.select do |n|
!rn_titles.include?(n.title)
end
# Find notes that have been added on the remote device since last sync.
ln_titles = local_notes.collect {|n| n.title}
delta[:new][:remote] = remote_notes.select do |n|
!ln_titles.include?(n.title)
end
# Find notes that have changed locally/remotely since last sync.
local_notes.each do |ln|
next unless rn = remote_notes.detect {|n| n.title == ln.title}
ln.counterpart = rn
rn.counterpart = ln
if rn.mod_date > ln.mod_date
delta[:changed][:remote] << rn
elsif rn.mod_date < ln.mod_date
delta[:changed][:local] << ln
end
end
unless delta[:new][:local].empty?
puts "Adding to MobileNotes.app:"
delta[:new][:local].each do |ln|
rn = IphodNote.clone(ln)
rn.save!
puts " #{rn.title}"
end
end
unless delta[:new][:remote].empty?
puts "Adding to bash_notes:"
delta[:new][:remote].each do |rn|
ln = BashNote.clone(rn)
ln.save!
puts " #{ln.title}"
end
end
unless delta[:changed][:local].empty?
puts "Updating in MobileNotes.app:"
delta[:changed][:local].each do |ln|
ln.counterpart.clone(ln)
ln.counterpart.save!
puts " #{ln.title}"
end
end
unless delta[:changed][:remote].empty?
puts "Updating in bash_notes:"
delta[:changed][:remote].each do |rn|
rn.counterpart.clone(rn)
rn.counterpart.save!
puts " #{rn.title}"
end
end
ensure
SQL_IFACE.close
end
end
class Note
attr_accessor :title, :body, :mod_date, :counterpart
def initialize(atts)
atts.each_pair { |key, value| self.send("#{key}=", value) }
end
def self.clone(other_note)
n = new(
'title' => other_note.title,
'body' => other_note.body,
'mod_date' => other_note.mod_date
)
n.counterpart = other_note
n
end
def self.all
# override in subclass
end
def save!
# override in subclass
end
def clone(other_note)
self.title = other_note.title
self.body = other_note.body
self.mod_date = other_note.mod_date
end
end
class BashNote < Note
def self.notes_dir=(val)
@@notes_dir = val
end
def self.notes_dir
@@notes_dir
end
def self.all
files = Dir.glob(File.join(@@notes_dir, '*.note'))
files.collect do |file|
new(
'title' => File.basename(file, '.note'),
'body' => IO.read(file),
'mod_date' => File.new(file).mtime.to_i
)
end
end
def save!
filename = File.join(self.class.notes_dir, self.title + '.note')
File.open(filename, 'w') { |f| f.write(self.body) }
File.utime(Time.now, Time.at(self.mod_date), filename)
end
end
class IphodNote < Note
attr_accessor :row_id, :native_body, :native_mod_date
def initialize(atts)
self.title = atts['title'].strip
if atts['native_body']
self.native_body = remove_iphod_title(atts['native_body'])
elsif atts['body']
self.body = atts['body']
end
if atts['native_mod_date']
self.native_mod_date = atts['native_mod_date'].to_i
else
self.mod_date = atts['mod_date'].to_i
end
self.row_id = atts['row_id'].to_i
end
def self.all
results = SQL_IFACE.query(
'SELECT Note.rowid, title, creation_date, data ' +
'FROM Note JOIN note_bodies on Note.rowid = note_id'
)
result_array = results.strip.split("\n\n")
result_array.collect do |result|
atts = {}
{
'row_id' => 'rowid',
'title' => 'title',
'native_mod_date' => 'creation_date',
'native_body' => 'data'
}.each_pair do |att, column|
match = result.match(/^\s*#{column} = (.*?)$/)
atts[att] = match[1] if match
end
new(atts)
end
end
def save!
return if !title || !body
if self.row_id > 0
SQL_IFACE.query(
"UPDATE Note SET creation_date = ?, title = ?, summary = ? " +
"WHERE rowid = ?",
self.native_mod_date,
self.title,
self.title,
self.row_id
)
SQL_IFACE.query(
"UPDATE note_bodies SET data = ? WHERE note_id = ?",
prepend_iphod_title(self.native_body),
self.row_id
)
else
SQL_IFACE.query(
"INSERT INTO Note VALUES (?, ?, ?)",
self.native_mod_date,
self.title,
self.title
)
result = SQL_IFACE.query(
"SELECT rowid FROM Note WHERE title = ?",
self.title
)
self.row_id = result.match(/rowid = (\d+)/)[1].to_i
SQL_IFACE.query(
"INSERT INTO note_bodies VALUES (?, ?)",
self.row_id,
prepend_iphod_title(self.native_body)
)
end
rescue => e
puts "Remote Save failed for: #{self.title}\n#{e.inspect}"
end
def body=(val)
@native_body = self.class.iphod_format(val)
@body = val
end
def native_body=(val)
@body = self.class.text_format(val)
@native_body = val
end
def mod_date=(val)
@native_mod_date = val - self.class.iphone_epoch_offset
@mod_date = val
end
def native_mod_date=(val)
@mod_date = val + self.class.iphone_epoch_offset
@native_mod_date = val
end
private
def self.iphod_format(text)
operations = [
[/&/, '&'],
[/, '<'],
[/>/, '>'],
[/^\s*[\n$]/, '
'],
[/ /, " "],
[/^(.*?)\s*?[\n$]/, '