Skip to content

Instantly share code, notes, and snippets.

@tarao
Forked from hotchpotch/hatena_diary_fs.rb
Created November 2, 2009 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tarao/224325 to your computer and use it in GitHub Desktop.
Save tarao/224325 to your computer and use it in GitHub Desktop.
HatenaDiaryFS
#! /usr/bin/env ruby
require 'rubygems'
require 'pit'
require 'fusefs'
require 'atomutil'
require 'time'
module REXML
class Text
def clone
# bug fix: prevent entities from beeing escaped again
return Text.new(@string, true, nil, @raw)
end
end
end
class Time
ENTRY_FORMAT = '%Y-%m-%dT%H:%M:%S'
def to_entry_name
return strftime(ENTRY_FORMAT)
end
def self.entry_name(name)
return Time.iso8601(name)
end
end
module Atompub
class HatenaClient < Client
XMLNS = 'http://www.hatena.ne.jp/info/xmlns#'
DIARY_ATOM = 'http://d.hatena.ne.jp/%s/atom'
def publish_entry(uri)
@hatena_publish = true
update_resource(uri, ' ', Atom::MediaType::ENTRY.to_s)
ensure
@hatena_publish = false
end
def get_body(entry)
get_resource(entry.edit_link)
begin
return @rc.get(XMLNS, 'syntax').text
rescue
return @rc.content.body
end
end
def remove_entry(entry)
delete_entry(entry.edit_link)
end
def write_entry(col, entry, title, body, update)
if entry
entry.title = title
entry.content = Atom::Content.new(:body => body, :type => 'text')
update_entry(entry.edit_link, entry)
else
entry = Atom::Entry.new(:title => title,
:content => body,
:updated => update)
create_entry(col, entry)
end
end
private
def set_common_info(req)
req['X-Hatena-Publish'] = 1 if @hatena_publish
super(req)
end
end
end
class HatenaDiaryFS < FuseFS::MetaDir
def initialize(config)
super()
@config = config
auth = Atompub::Auth::Wsse.new(config)
@client = Atompub::HatenaClient.new(:auth => auth)
service_uri = Atompub::HatenaClient::DIARY_ATOM % config[:username]
@service = @client.get_service(service_uri)
@collections = {}
@service.workspace.collections.each do |col|
colname = File.basename(col.href)
mkdir(File.join('', colname), Collection.new(col.href, @client))
end
end
class Collection
CREATE_PATH = 'create'
def initialize(collection, client)
@collection = collection
@client = client
end
def feed
return @client.get_feed(@collection)
end
def entries
return @entries ||= feed.entries
end
def find_entry(name)
name = name.to_entry_name if name.kind_of?(Time)
return entries.find do |e|
e.updated.to_entry_name == name
end
end
def contents(path)
return [CREATE_PATH] + (entries.map{|e| e.updated.to_entry_name} || [])
end
def file?(path)
return path == CREATE_PATH || !!find_entry(path)
end
def read_file(path)
begin
e = find_entry(path)
if e
return ['* ' + e.title, @client.get_body(e)].join("\n\n")
else
return ''
end
rescue Exception => e
puts(['read error', e, e.backtrace])
end
end
def can_delete?(path)
true
end
def delete(path)
begin
entry = find_entry(path)
@client.remove_entry(entry)
@entries = nil
rescue Exception => e
puts(['delete error', e, e.backtrace].flatten)
end
end
def can_write?(path)
begin
return !!(path == CREATE_PATH || Time.entry_name(path))
rescue ArgumentError
return false
end
end
def write_to(path, body)
begin
title, body = body.split("\n", 2)
if title && body
title = title.sub(/^\*\s*/, '')
body = body.sub(/^\n+/, '')
entry = find_entry(path)
u = (path == CREATE_PATH) ? Time.now : Time.entry_name(path)
@client.write_entry(@collection, entry, title, body, u)
@entries = nil
end
rescue Exception => e
puts(['write error', e, e.backtrace].flatten)
end
end
end
end
if ARGV.length < 1
puts("Usage: #{File.basename($0)} mountpoint")
exit
end
config = Pit.get('hatena', :require => {
:username => "your Hatena ID",
:password => "your Hatena password"
})
fs = HatenaDiaryFS.new(config)
FuseFS.set_root(fs)
FuseFS.mount_under(ARGV.first)
FuseFS.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment