ドキュメント管理に悩む3 Dropboxの応用

前回の結論で、公開するテキスト(html)ドキュメントをDropboxに置くことに決めた。

  • 公開されてもよいoffice文書は Google Drive
  • zipやアプリ、音楽、動画などの公のコンテンツやアーカイブは SkyDrive
  • それ以外は Dropbox

Dropboxは publicフォルダに置いたhtmlファイルを

http://dl.dropbox.com/u/****/index.html

という感じでWebアクセスできる。これが他のサービスに無いありがたい機能だ。

しかし弱点は静的なファイルしか置けないこと。javascriptは動くが、サーバーサイドのスクリプトが動かない。
なので、たとえば「docs/ 以下のファイル一覧を表示」とか検索機能とかができない。そうなると「そういうことをやりたければpukiwikiだろ」という話になってループしてしまう。なんとかならないものか・・・。

そこで考えました。コンテンツはDropboxに置いておいて、Webサービスレンタルサーバーが担えばいいんじゃないかと。
レンタルサーバーの WebAppが Dropbox のコンテンツを参照して、加工なりなんなりしてクライアントにレスポンスする。なかなかよいのでは!

Dropboxのプライベートなコンテンツにアクセスするには認証してAPIを叩く必要があるけど、パブリックなコンテンツはそんなもの不要。phpなりsinatraなりで簡単に実現できそう。

■やってみる

ドキュメント管理に悩む2

続き。

■各社サービスの違い

  • 容量
  • 利用規約(コンテンツの取り扱い)
  • Web閲覧

容量は実はたいした問題ではなく、下の2つの方が重要。で、この2つの条件でサービスの使い分けが決まってしまう。

利用規約は、コンテンツの公開範囲に影響する。

  • Google Drive, N Drive は、置いたコンテンツはサービスに利用されてもよいと認めることになる。GMailのようなもの。なので
    • プライベートなコンテンツは置けない。(怖いので)
    • 著作権的にまずいものは置けない。(法律的に)
  • SkyDrive は、他のユーザーに公開するとそのユーザーはそのコンテンツを自由に扱ってよい、となる。またMSの他のサービスに(それとは分からないで)こっそり利用される。よって
    • 著作権的にまずいものは公開してはいけない。
    • プライベートなコンテンツは置きたくない。(気分的に)
  • Dropboxは基本的にはローカルHDDやプライベートサーバーと同じ扱い。よって
    • プライベートなコンテンツや著作権的にまずいものも置いてよい。

結果、SkyDriveは公開さえしなければDropboxに近い。ただしMSによるスキャンを認めるので気分的にはDropboxの方が安心。

ということで、公開範囲の観点では、今の利用方法からの移行は以下の通り。

■Web閲覧
続いてWeb閲覧の観点から。

  • SkyDrive
    • windowsアプリを入れることでローカルPCと同期できるが、Windows Vista以降。
    • よってローカルのoffice文書を共有・公開するのには適している。
    • 基本的に招待制
    • パブリックなコンテンツは、フォルダごとにURL文字列を生成する
    • ランダムな文字列なのでweb閲覧の使い勝手は悪い
    • office文書を置いて、webで編集できる
    • テキストのアップロードはできるがブラウザで編集できない
  • Dropbox
    • publicフォルダに置いていけば、その階層通りにwebアクセスできる。
    • 基本、htmlのみ
    • webで編集はできない
  • Google Drive
    • SkyDriveとほぼ同じ。違いは WindowsアプリがWindows XPも対象なこと。
    • テキストのアップロードができない?(新規作成やアップロードは不可。アプリ経由ならできるかも)
  • N Drive

ということで、dropbox とそれ以外、という使い分けになる。

マトリックス

公開範囲による使い分け

文書の種類による使い分け

以上からマトリックスを作る。

Dropbox SkyDrive Google Drive N Drive
公開のtext(html) × × ×
非公開のtext(html) × ×
公開のoffice文書
非公開のoffice文書 × ×

N Drive はよく分からず、使い勝手が他のサービスより悪そうなので選外。
SkyDriveはoffice文書のweb編集に強いが、Windows XP との同期が中途半端なのでこれでは意味がない。
Google Driveは公開のoffice文書なら使い勝手がよさそう。

■結論

  • 公開されてもよいoffice文書は Google Drive
  • zipやアプリ、音楽、動画などの公のコンテンツやアーカイブは SkyDrive
  • それ以外は Dropbox

ドキュメント管理に悩む

以前から、自分用メモを色んなところに書きためてきている。
しかし保存場所や書式がその場その場でばらばらなので、いざ「これって前に調べてメモしたよな」と思っても見つけるのが難しくなってきた。

これじゃいかん。

具体的にどうなっているかというと、

こんなにある(´Д`;)

しかも保存形式がばらばらなので取り回しが効かない。たとえばpukiwikiで書いたメモを他に移すのって、pukiwiki書式で書いているので難しい。

保存する時にはそれなりに考えているはず。たとえば携帯からでも見たいものはpukiwikiに、とか。それにしても散らかりすぎている。

理想の形
じゃあどうなれば望ましいのか、ゴールイメージを書き起こしてみよう。

  • 閲覧
    • どこからでも見ることができる。
    • フォルダ構造に沿って階層構造で見ることができる
  • オーサリング(編集)
    • ローカルのエディタで書ける
    • 修正はweb上でもできる
    • wiki書式で書ける
    • 編集しながらリアルタイムにプレビューできる
  • 記述の書式
    • できるだけ表現力がほしい
    • Markdownは表現力が乏しすぎ
      • 色や表、定義リストは欲しい
    • pukiwiki形式は嫌い
    • 慣れているconfluenceがいいな
    • はてな記法でもいい
      • リロードする手間はOK
  • パブリッシュ(公開)
    • エクスプローラでファイルを所定のフォルダに置くだけですむ
    • もっと言えば、フォルダに置きながら編集できる

■既存サービスでは?
おそらく大きなポイントは「ローカルのエディタで編集→所定のフォルダに置けば公開」という要求だろう。たとえばpukiwikiは、この要件以外はほぼ満たしている。(あとは書式くらい。)
はてなダイアリーのようなブログもそうで、ローカルに書いたメモを「アップロード」するという一手間がかかる。
githubやbitbucketも同じで、これらは構成管理サービスの機能の一つという位置づけなので、ローカルで編集→pushという流れになるのが自然。
githubやbitbucketはリポジトリごとにwikiページを持ててそれはweb上で直接コンテンツを編集できるが、それならpukiwikiと変わらない。

「ローカルで編集〜」を満たすとなると利用できるサービスは限られる。Dropbox, EverNote, Google Drive のようなオンラインストレージ系だ。
オンラインストレージ系サービスが、そのストレージの中身をうまくwebで閲覧できれば目的が達成できるかもしれない。
すでに調べるまでもなく、これらのサービスでは単純なhtmlファイルならwebで閲覧できる。あとは残りの要求をどこまで満たせるか・・・。ということで、それを中心に調べることにした。続く。

ニコニコ動画のコメント(xml)を字幕ファイル(ssa)形式に変換

そんな神ツールが公開されている。

http://www6.atpages.jp/appsouko/work/smsub/

素晴らしい。

が、ときどきエラーになることがある。
調べてみると xml の chat エレメントの vpos 属性が負の値だとダメらしい。確かにエラーになる xml はそうなってる。

ということで python スクリプトで vpos が正の値のときだけコメントとして認識するように修正した。

280c280,281
<         comments.append(NicoComment(text, time_, cmd, by_author))
---
>         if time_ > 0:
>           comments.append(NicoComment(text, time_, cmd, by_author))

ruby 1.9 でATOKダイレクトを使う

ruby 1.9 の環境でATOKダイレクトプラグインをインストールしようとすると、以下のようなエラーが出てインストールすらできない。

セットアップ用件を満たすプラグインが存在しないため、セットアップを実行できません。
ロード中に以下のエラーが発生しました。
hoge.rb : [SCRIPT] スクリプトファイルのプラグインの文法チェックに失敗しました

調べてみると、どうもATOKダイレクトの本体が jcode.rb を必要としているらしい。
jcode.rb は 1.8 までは標準添付されていたが、1.9 では処理系が多言語対応したため不要になり、添付されていない。
そこで LOAD_PATH に適当に 1.8.x の jcode.rb を置く。これでインストールは成功する。

ただし、インストールは成功するものの 1.9 + jcode.rb というイレギュラーな環境では思わぬ副作用があるかもしれないので、できれば jcode.rb は空ファイルにしておきたい。
が、ATOKダイレクトが jcode.rb の関数を使っているので完全に空にはできない。

今のところ判明しているのは jlength。とりあえずこの関数だけ残して使い続けてみて、また別のプラグインで NoMethodError が出たときに追記していけばいいかと。

本当はATOKダイレクト自体が Ruby 1.9 に対応してくれるのが一番だけど、1.8 のことも考えると難しそうなので「最新版の言語を使う人間の宿命」としてこちら側が覚悟しないといけないのだろう。


言わずもがなだが、プラグインスクリプト側が(jcodeに限らず)Ruby 1.9 に対応してないとどうしようもない。これも自助努力で対応すべし、と。

WikiPedia で脚注をポップアップ

絶対にあると思ってネットを探したら、意外にすぐに見つからなかったので。

// ==UserScript==
// -*- mode:JScript; Encoding:utf8n -*-
// @name           Wikipedia.citePopup
// @namespace      http://d.hatena.ne.jp/p-arai/
// @description    Add pop-up on reference to footnote
// @include        http://ja.wikipedia.org/wiki/*
//
// @author         p-arai
// @version        2009.02.17

(function () {

  // retrive array of cites
  var cites = new Array();
  var lis = document.getElementsByTagName("li");
  for (var i=0; i < lis.length; i++){
    var li = lis[i];
    if (li.id.match(/^cite_note-/)){
      cites.push(li);
    }
  }

  // retrive and modify reference element
  var hrefs = document.getElementsByTagName("a");
  for (var i=0; i < hrefs.length; i++){
    var a = hrefs[i];
    if (a.href.match(/#cite_note-(\d+)$/)){
      var index = RegExp.$1;
      if (typeof a.textContent != "undefined") {
        a.title = cites[index].textContent.replace(/^[^]\s*/, "");
      } else {
        a.title = cites[index].innerText.replace(/^[^]\s*/, "");
      }
    }
  }

})();

WikiPedia でタレントの写真を表示

Wikipedia でタレントを検索したら、Yahoo! タレントプロフィールから写真を貼り付ける greasemoney スクリプト。ただし誤爆多し。
http://ja.wikipedia.org/wiki/%E6%9C%A8%E6%9D%91%E3%82%AB%E3%82%A8%E3%83%A9
とか。なんでやねん。(アルゴリズム改良中)

// ==UserScript==
// -*- mode:JScript; Encoding:utf8n -*-
// @name           wikipedia.yahooTalentImage
// @namespace      http://d.hatena.ne.jp/p-arai/
// @description    Add pop-up on reference to footnote
// @include        http://ja.wikipedia.org/wiki/*
//
// @author         p-arai
// @version        2009.02.17
// ---------------------------------------------------------
// This script is a derived version of wikipedia.googleimage
// http://www.kagami.org/diary/2007-09-16-1.html
// ---------------------------------------------------------

(function () {
  var debug = false;

  var baseurl = 'http://search.yahoo.co.jp/search?p=';
  var word = document.title.replace(/\s+-\s+Wikipedia/, "");
  var searchurl = baseurl + word + "+%E3%83%97%E3%83%AD%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB+talent.yahoo.co.jp";
  var img = document.createElement('img');
  img.alt = 'Yahoo: ' + word;

  var a = document.createElement('a');
  a.appendChild(img);
  a.href = searchurl;
  
  var div = document.createElement('div');
  div.appendChild(a);

  if (debug){
    var debugArea = document.createElement('textarea');
    div.appendChild(debugArea);
  }

  var h1 = document.getElementsByTagName('h1')[0];
  h1.parentNode.insertBefore(div, h1.nextSibling);

  var profileURL;

  GM_xmlhttpRequest({
  method: 'GET',
  url: searchurl,
  headers: { 'User-agent': 'Mozilla/4.0 (compatible)' },
  onload: function(responseDetails) {
    var responseText = responseDetails.responseText.replace(/<\/b><b>/ig, "");
    if (debug) debugArea.value = responseText;
    reProfileURL = new RegExp('talent.yahoo.co.jp/talent/.+?\.html', "i");
    reProfileTitle = new RegExp(word + '.*?の<b>プロフィール', "i");
    reImgL = new RegExp('http://i.yimg.jp/images/talent/large/.+?jpg', "i");
    reImgM = new RegExp('http://i.yimg.jp/images/talent/medium/.+?jpg', "i");
    if (responseText.match(reImgM)) {
    if (debug) window.alert("reImgM found.");
      // alert(responseText.match(reImgM));
      img.src = RegExp.lastMatch.replace(/medium/, "large");
    } else if (responseText.match(reProfileTitle) &&
               responseText.match(reProfileURL)) {
      responseText.match(reProfileURL);
      if (debug) window.alert("rePofile found.");
      profileURL = 'http://' + RegExp.lastMatch;
      if (debug) alert("profileURL=" + profileURL);
      GM_xmlhttpRequest({
      method: 'GET',
      url: profileURL,
      headers: { 'User-agent': 'Mozilla/4.0 (compatible)' },
      onload: function(responseDetails) {
        if (debug) alert(responseDetails.responseText.match(reImgL));
        if (responseDetails.responseText.match(reImgL)) {
          img.src = RegExp.lastMatch;
        }
      }
      });
    } else { // no image
      if (debug) alert("no image");
    }
  }
  });
})();