ブログトップ 記事一覧 ログイン 無料ブログ開設

それはそれ。これはこれ。 このページをアンテナに追加 RSSフィード

2003 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 03 | 04 | 05 | 06 | 07 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 11 | 12 |
2013 | 01 | 02 | 04 | 05 | 07 | 08 | 10 | 11 | 12 |
2014 | 02 | 03 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 05 | 06 | 07 | 11 |
2016 | 07 |

2016-07-16(土)

[][][] MS Exchange/OutlookカレンダーGoogle Calendarの同期 (続編)  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 (続編)を含むブックマーク  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 (続編)のブックマークコメント

id:otn:20150901MS Exchange/OutlookカレンダーGoogle Calendarの同期」のその後。

google-api-client が 0.9 になってそのままでは動かなくなった。0.8のまま使っていたのだが、認証がおかしくなった(毎回認証しないとタイムスタンプがおかしいというエラーになる)ので、0.9 対応修正。いろいろあった。

認証については、googleauth の README.md を参考に。しかし、以前のように「ブラウザ自動的に開いて、認証するとアプリが続行」という風には出来ず、自分ブラウザを開いて、認証したあと、そこに表示された文字列入力しないといけない。これは不便だがしょうが無い。

あともAPIが色々変わっており、ソースを追ったり、gemデバッグプリントを入れたりしてなんとかなった。手こずったのがリマインダ。リマインダメソッド指定キーが、"method" から :reminder_method に変わっている!


#! ruby
CAL_ID = "xxxxxxxxxxxxxxxxx@gmail.com" #GOOGLEアカウントのメールアドレス
DAYS = 30
AUTH_FILE = "authfile.yaml"
SECRET_FILE = "client_secret.json"

ENV["http_proxy"]  = "http://user:pass@proxyserver:port" # proxyの設定
ENV["https_proxy"] = "http://user:pass@proxyserver:port" # proxyの設定

Dir.chdir File.dirname($0)

require "googleauth"
require "googleauth/stores/file_token_store"
require "google/apis/calendar_v3"
require "google/api_client/client_secrets"
require "win32ole"

class Event
  def to_s # デバッグ用に書いたもの
    [@start.strftime("%Y-%m-%d %H:%M"),
    @end.strftime("%Y-%m-%d %H:%M"),
    @allday.to_s[0],
    @reminder.to_s,
    @title, @body.gsub(/\s/," "), @location].join(",")
  end

  def ==(other)
    @start    == other.instance_variable_get(:@start)  and
    @end      == other.instance_variable_get(:@end)    and
    @allday   == other.instance_variable_get(:@allday) and
    reminder_eql?(@start, @reminder, other.instance_variable_get(:@reminder)) and
    @title    == other.instance_variable_get(:@title)  and
    @body     == other.instance_variable_get(:@body)   and
    @location == other.instance_variable_get(:@location)
  end

  def delete
    Gcal.delete(@id)
  end

  def add
    event = {
      summary:      @title,
      description:  @body,
      start:        start,
      end:          ende,
      location:     @location,
      reminders:    reminder,
      source:       {title: "Exchange", url: "http://localhost"}
    }
    Gcal.add(event)
  end

  def is_holiday
    @allday and @location=="日本"
  end

  private
  def start
    if @allday
      {date: @start.strftime("%Y-%m-%d")}
    else
      {date_time: @start.iso8601}
    end
  end
  def ende
    if @allday
      {date:  @end.strftime("%Y-%m-%d")}
    else
      {date_time:  @end.iso8601}
    end
  end
  def reminder
    if @reminder
      {use_default: false, overrides: [{reminder_method: :popup, minutes: @reminder}]}
    else
      {use_default: false}
    end
  end
  def reminder_eql?(start, x, y)
    @@now ||= Time.now
    ( x == y ) or
    # リマインダー時刻を過ぎている場合はリマインダーの有無を無視
    ( x and not y and start - x < @@now ) or
    ( y and not x and start - y < @@now )
  end
end

# Googleカレンダー
class Gcal < Event
  def self.get_auth(calendar_id, auth_filename, secret_filename)
    @@cal_id = calendar_id

    client_id   = Google::Auth::ClientId.from_file(SECRET_FILE)
    scope       = "https://www.googleapis.com/auth/calendar"
    token_store = Google::Auth::Stores::FileTokenStore.new(file: AUTH_FILE)
    authorizer  = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)

    credentials = authorizer.get_credentials(@@cal_id)
    if credentials.nil?
      url = authorizer.get_authorization_url(base_url: "urn:ietf:wg:oauth:2.0:oob")
	  system(%Q|CMD /c start "" "#{url}"|)
      print "Enter the resulting code: "
      code = gets
      credentials = authorizer.get_and_store_credentials_from_code(
                    user_id: @@cal_id, code: code, base_url: "urn:ietf:wg:oauth:2.0:oob")
    end

	@@client = Google::Apis::CalendarV3::CalendarService.new
	@@client.authorization = credentials
  end

  def self.delete(id)
    @@client.delete_event(@@cal_id, id)
  end

  def self.add(event)
    @@client.insert_event(@@cal_id, Google::Apis::CalendarV3::Event.new(event))
  end

  def self.is_exchange(item)
    item.source and item.source.title == "Exchange"
  end

  def self.list(from, to)
    @@client.list_events(@@cal_id,
      order_by:  "startTime",
      time_min:  from.iso8601,
      time_max:  to.iso8601,
      single_events:  "True"
    ).items
  end

  def initialize(event)
    if event.start.date
      @start  = Time.parse(event.start.date)
      @end    = Time.parse(event.end.date)
      @allday = true
    else
      @start  = event.start.date_time.to_time
      @end    = event.end.date_time.to_time
      @allday = false
    end
    if event.reminders.use_default
      @reminder = 10 # 正確にはそのユーザーのデフォルト値を取得する必要がある
    elsif event.reminders.overrides
      @reminder = event.reminders.overrides[0].minutes
    else
      @reminder = nil
    end
    @title = event.summary || ""
    @body  = event.description || ""
    @location = event.location || ""
    @id = event.id
  end
end

# Exchangeカレンダー
class Ecal < Event
  FolderCalendar = 9
  def self.list(from, to)
    @@calendar ||= WIN32OLE.new("Outlook.Application")
      .GetNamespace("MAPI").GetDefaultFolder(FolderCalendar)

    items = @@calendar.Items
    items.Sort "[Start]"
    items.IncludeRecurrences = true
    item = items.Find(%Q/[Start] < "#{to.strftime("%Y-%m-%d %H:%M")}" AND [End] >= "#{from.strftime("%Y-%m-%d %H:%M")}"/)

    Enumerator.new do |y|
      while item 
        y << item
        item = items.FindNext
      end
    end
  end

  def initialize(event)
    @start = event.Start
    @end   = event.End
    @allday = event.AllDayEvent
    @reminder = event.ReminderSet ? event.ReminderMinutesBeforeStart : nil
    @title  = event.Subject.encode(Encoding::UTF_8)
    @body   = event.Body.encode(Encoding::UTF_8)
    @location = event.Location.encode(Encoding::UTF_8)
  end
end

# 期間設定
today = Time.now
time_min = today - 3600*24*DAYS
time_max = today + 3600*24*DAYS

# Exchangeイベント取得
e_events = Ecal.list(time_min, time_max).map{|ev| Ecal.new(ev)}.reject(&:is_holiday)

# Google認証
Gcal.get_auth(CAL_ID, AUTH_FILE, SECRET_FILE)

# Googleイベント取得
g_events = Gcal.list(time_min, time_max)
  .select{|ev| Gcal.is_exchange(ev)}.map{|ev| Gcal.new(ev)}

# イベント削除
g_events.reject{|ev| e_events.include?(ev)}.each{|ev| ev.delete}

# イベント追加
e_events.reject{|ev| g_events.include?(ev)}.each{|ev| ev.add}
トラックバック - http://d.hatena.ne.jp/otn/20160716

2015-11-24(火)

[]ディスク領域不足のバルーンを表示させない ディスク領域不足のバルーンを表示させないを含むブックマーク ディスク領域不足のバルーンを表示させないのブックマークコメント

RAMDISKキャッシュを置いているようなケースだと、使用量をアプリ側で設定できるので、領域不足になることはないのだが、容量一杯まで使おうとすると、頻繁に警告のバルーンが出てくる。

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer
    NoLowDiskSpaceChecks    REG_DWORD    0x1

まいどまいど 2016/01/21 10:04 坂下です。
中西からのメールを読んでください

2015-07-05(日)

[]call無しの他バッチスクリプト呼び出し call無しの他バッチスクリプト呼び出しを含むブックマーク call無しの他バッチスクリプト呼び出しのブックマークコメント

バッチスクリプトから他のバッチスクリプトを呼び出すときに、callで呼び出さないと戻ってこない物だと思っていた。

<<foo.bat>>
bar.bat
echo foo ・・・・・実行されない
<<bar.bat>>
echo bar

しかし、forだと実行される。

<<foo.bat>>
for %%A in (a b c) do bar.bat&echo foo ・・・・・echo fooまで含めてちゃんと3回実行される
echo after for ・・・・・これは実行されない
<<bar.bat>>
echo bar

ifも。

<<foo.bat>>
if a==a bar.bat&echo foo ・・・・・echo fooも実行される
echo after if ・・・・・これは実行されない
<<bar.bat>>
echo bar

構文解析単位で実行が保証されると言うことか。

トラックバック - http://d.hatena.ne.jp/otn/20150705

2015-06-28(日)

[]うるう秒対応 うるう秒対応を含むブックマーク うるう秒対応のブックマークコメント

借りているVPSでの、うるう秒対応

# crontab -l
59 8 1 7 * bash -c "sleep 40;/sbin/shutdown -r now"

shutdown のTIMEで秒を指定する方法が分からない。

ググると、-t で指定できると書いてあるページもあるが、CentOS6.6のshutdownではそういうオプションは無い。

トラックバック - http://d.hatena.ne.jp/otn/20150628

2015-05-05(火)

[]ノートPCディスク交換 ノートPCディスク交換を含むブックマーク ノートPCディスク交換のブックマークコメント

ノートPCを買って4年近くになるので、ディスク交換。HDD 512GBから1TBに。

SSDにすることも考えたけど、結局HDDに。


最初は、DVDリカバリーしてきれいに入れ直そうと思ったけど、いざやろうとなるとめんどくさくなって、コピーすることにした。

ツールは、前回と同じ EASEUS DiskCopy http://www.easeus.com/disk-copy/ で。

現状は、Dドライブ拡張パーティション論理ドライブになっているので、パーティションコピーにすることにした。ところが、MBR書き込みがめんどくさそうなので、一旦はDVDリカバリーして、新HDDから起動できることを確認してからパーティションコピーで。


手順は、

1.新HDDを外付けケースに入れてパーティションの設定(diskpartコマンド

2.新HDDを内蔵させて、DVDリカバリーでCドライブのみリカバリーして起動確認

3.EASUS DiskCopyをUSBインストールして、USB起動

4.旧HDDを外付けケースに入れて、そこから内蔵HDDにEASEUS DiskCopyでパーティションコピー

5.新HDDで起動。Dドライブドライブ名が付かなかったので(何故?)、Dと付ける

6.再起動

7.パーティションコピーパーティションが旧HDDの大きさに縮んだので、パーティションサイズ拡張

8.再起動して完了

最初DVDリカバリーじゃなくてディスクコピーMBRコピーして起動しようとしたけど、起動せず。


速度は、シーケンシャルで2倍、ランダムで1.3倍くらい。


2015-05-17追記

システムの復元の設定が無効になっていた。なおかつ古いドライブの設定が残っていた。

Cドライブ有効、Dドライブ無効にする。

トラックバック - http://d.hatena.ne.jp/otn/20150505

2015-03-12(木)

[][] RubyInstaller2.2をインストールしてgem関連でエラー  RubyInstaller2.2をインストールしてgem関連でエラーを含むブックマーク  RubyInstaller2.2をインストールしてgem関連でエラーのブックマークコメント

RubyInstallerで2.2.1が出たのでインストールした。前のバージョンはRuby213にリネームして残しておく。

SSL3問題(Windowsgem install出来ない問題)については、Rubyディレクトリの外に正しい証明書ファイルを置いて、環境変数 SSL_CERT_FILE でそれを指しているので、Rubyを丸ごと差し替えても問題なし。ただ、環境変数をアンセットしても gem install 出来るようなので、標準添付の証明書問題なくなったようだ。


今のところ問題点は3つ。


まず、gem install pryで、エラーが発生。

gem instal pry pry-doc
ERROR:  While executing gem ... (Gem::RemoteFetcher::FetchError) 
    bad response Not Found 404 (https://rubygems.global.ssl.fastly.net/quick/Marshal.4.8/pry-0.10.1-x86-mingw32.gemspec.rz)

ググってもよくわからないが、mingw32版のpryの管理情報が不整合を起こしているのではないかというのが結論

このサイト fastly.netけがおかしいのかと思い、grep -R fastly.net \Ruby\lib記述ファイルを探すと update_bundled_ca_certificates.rb に記述があるのでそこをコメントにしても同じ情況

mingw32版バイナリじゃなくてソースからインストールすれば良いのではないかと思い、

gem install pry pry-doc --platform source

と、platformにsourceと指定してみるとうまくいった。


2つ目も同じくgemsで、net-ssh。Ruby2.2からdlライブラリが廃止されたので動かなくなった。

これはしょうがないので、使っているスクリプトについては当面はRuby2.1.3を使い続けることにして、バッチファイルで皮をかぶせた。


3つ目は、Nokogiri。

ruby -e "require 'nokogiri'"
D:/Ruby/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- nokogiri/nokogiri (LoadError)

これもしょうがないので、使っているスクリプトについては当面はRuby2.1.3を使い続けることにして、バッチファイルで皮をかぶせた。


まだ、いろいろありそう。

2015-03-11(水)

[] MinGW/MSYSインストール  MinGW/MSYSインストールを含むブックマーク  MinGW/MSYSインストールのブックマークコメント

今まで、UnixツールGnu-Win32パッケージ http://gnuwin32.sourceforge.net/packages.html を使っていたが、RubyInstallerでインストールしたRubyのGemsインストールのためにMinGWのDevKitを入れて併用していた。

MinGWのパッケージ管理ツールがあるというのに気づいたので、それでMinGW/MSYSを入れて、Gnu-Win32とRubyInstallerのDevKitを使わないことにする。

http://sourceforge.net/projects/mingw/files/Installer/ からmingw-get-setup.exeダウンロードして実行。

インストールしたMinGW Installation Managerを起動して、パッケージを選択する。

f:id:otn:20150312192109p:image

左側ペインで、Basic Setup(大まかな選択)と、All Packages(パッケージ個別の選択)が撰べるが、Basic Setupで、mingw-developer-toolkit と mingw32-base と msys-base を選択(右クリックして Mark for Installation)して、InstallaionメニューからApply Changesで各パッケージをインストールされる。

環境変数PATHへの設定をする機能が無いようなので、自分で \MinGW\bin と \MinGW\msys\1.0\bin を追加する。

トラックバック - http://d.hatena.ne.jp/otn/20150311

2015-02-11(水)

[] iTunes12でカラムブラウザの表示方法  iTunes12でカラムブラウザの表示方法を含むブックマーク  iTunes12でカラムブラウザの表示方法のブックマークコメント

「iTunes12でのカラム表示」http://takabo.net/2014/10/21/itunes12-column-browser/ より:

  1. 真ん中上で「プレイリスト」を選択
  2. 右上で「曲」を選択
  3. メニューバーで[表示]→[カラムブラウザ]→[カラムブラウザを表示]

itunes12.4itunes12.4 2016/06/01 13:13 12.4ではすでに違いますよね!

トラックバック - http://d.hatena.ne.jp/otn/20150211

2015-01-01(木)

[] カスペルスキーの「入力情報漏洩防止が有効です」問題  カスペルスキーの「入力情報の漏洩防止が有効です」問題を含むブックマーク  カスペルスキーの「入力情報の漏洩防止が有効です」問題のブックマークコメント

ノートンインターネットセキュリティから カスペルスキーインターネットセキュリティー に切り替えて困ったこと。

Amazon検索欄に文字入力しようとすると、カスペルスキーが介入して「入力情報漏洩防止が有効です」との表示を出し、いったんカスペルスキー入力されてからブラウザ入力される。


しかもバグがある!


レジストリの設定で、英数キーをコントロールキーにしているのだが、それが認識されず、ctrl-V としているつもりでも、単に v や V と入力されしまう。これには困った。


設定でオフに出来るだろうと思って調べると、

「設定」画面の、「詳細」を選んで、「データ入力の保護」。「入力情報漏洩防止」がこの機能のようだ。

全部オフにしてもいいのだが、「カテゴリ編集」というのがあるので、選択的にオフに出来そう。

カテゴリ編集」画面でカテゴリ別のオン・オフも出来るが、「特定Webサイトの設定」というのがあるので、それを選ぶ。

「追加」で amazon.co.jp の「サイト全体」を「保護しない」設定にして、完了

トラックバック - http://d.hatena.ne.jp/otn/20150101

2014-12-31(水)

[] 家族ガラケースマフォ並存移行  家族のガラケーをスマフォに並存移行を含むブックマーク  家族のガラケーをスマフォに並存移行のブックマークコメント

5年弱使っている家族auガラケーが、ボタン類がぼろぼろになって使用に耐えなくなってきていると言われたのが11月末。

この際スマフォが使いたいという声もあり、切り替えを考えることにした。

ただし条件として、事情があり、1月末までは今の電話番号とメールアドレス(@ezweb.ne.jp)を使い続けたいと言うこと。

で、その条件があると、1月末まで待つか、au内で機種変という所だが、転送逃げられないか調べてみた。

電話着信を新スマフォ転送

メールGmailメールアドレス転送

が出来ればいいわけだが、調べるとどちらも出来る。docomoだとメール転送できないようだ。

ということで、1月末までau契約を残しておいて、MVNOスマフォを使うことにした。


MVNO各種色々あるが、最終的に選んだのは、so-netモバイルLTEスマホセット http://www.so-net.ne.jp/access/mobile/lte/campaign/sp.html

スマフォ別で月1,890円(1.5GB制限)

・Android4.4のスマフォが、実質14,160円で買える

・すでにso-netの会員だと200円安い

カスペルスキー5台版を無料で使えるので、月額換算で約320円安いことになる(今日現在のAmazon価格で3,874円)

今使っているノートンインターネットセキュリティーの期限が1月初なので、ちょうど良い。

ただ、それまでso-netメール会員(月100円)だったのだが、これだとso-netモバイル契約できないそうで、モバイル会員(月200円)に切り替え。

ということで、実質月額は1,890-100-320=1,470円。


セットのスマフォ ZTE Blade Vec は薄くて悪くないが、欠点がいくつかある。

バッテリーが交換できない・・・まあ、iPhoneもそうだし

microSDカードが入れられない・・・今回は問題なし

・通知があったことを示すLEDが暗い・・・これは問題有り。少し離れたところからだと見えない(個体差か製品仕様か不明)


1月末にau契約を解除または基本料金のみで解除月まで伸ばすか。

次は、自分スマフォMVNOに切り替え。何にしよう。

トラックバック - http://d.hatena.ne.jp/otn/20141231

2014-12-30(火)

[] 簡易メーリングリストマネージャ  簡易メーリングリストマネージャを含むブックマーク  簡易メーリングリストマネージャのブックマークコメント

メーリングリスト運用したいので、FML を使おうとしたのだが、機能が多すぎでいまいち使いにくい。

ということで、最小限の機能を持ったマネージャ作ってみた

機能としては、これくらい。

・1メーリングリスト1スクリプト

・メンバー以外から投稿拒否する(弱気にFromヘッダで判断

サブジェクトは連番を入れて書き換える

Reply-To: や Sender: は設定する

・それ以外は素通し(文字コード添付ファイルもそのまま)

・送った物を一応保存しておく

管理機能は無く、メンバー管理は、直接ファイル編集する


gems mail を使おうかとも思ったが、Subjectのエンコードを変更してしまうみたいで、やめた。


厳密にやるのはめんどくさいので、手を抜いた。

#!/usr/local/bin/ruby
require "net/smtp"
require "base64"

ML = "foo"
DOMAIN = "example.jp"

MLADDR = "#{ML}@#{DOMAIN}"
OWNER  = "#{ML}-owner@#{DOMAIN}"
SUBJ   = "[#{ML.upcase} %06d] %s"

NOTMEMBER = <<EOT
From: #{OWNER}
To: %<dest>s
Subject: You %<dest>s are not member #{ML} ML
Mime-Version: 1.0
Content-Type: text/plain; charset="iso-2022-jp"

あなたはこのメーリングリスト <#{MLADDR}> のメンバーではありません。

メーリングリストへの登録は、
#{OWNER} にメールしてください。
EOT

DIR        = "/var/spool/ml/"

MAILDIR    = File.join(DIR,ML,"spool")      #メール保存ディレクトリ
SEQFILE    = File.join(DIR,ML,"seq")        #連番管理ファイル
MEMBERS    = File.join(DIR,ML,"members")    #投稿可能アドレス群
RECIPIENTS = File.join(DIR,ML,"recipients") #配布先アドレス群


def get_seq
  File.open(SEQFILE, File::RDWR|File::CREAT, 0644) do |f|
    f.flock(File::LOCK_EX)
    seq = f.read.to_i + 1
    f.rewind
    f.puts seq
    f.flush
    f.truncate(f.pos)
    seq
  end
end

def read_addrs(file)
    IO.readlines(file).map{|x| x.sub(/#.*/,"").strip}.grep(Header::MAILre)
end

def not_member(dest)
  if dest
    Net::SMTP.start("localhost", 25, OWNER) do |smtp|
      smtp.send_message(sprintf(NOTMEMBER,dest: dest)
        .encode(Encoding::ISO_2022_JP).force_encoding(Encoding::ASCII_8BIT),
        OWNER, [dest,OWNER])
    end
  end
end

class Header < String
  MAILre = %r<[A-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*>i
  SUBJre   = %r<^\s*(Re:|Fw:)?(\s*\[#{ML} \d+\]\s*((Re:|Fw:)\s*)*)+>i
  ENCODEre = %r<=\?([A-Z0-9_-]+)\?B\?([A-Z0-9/+=]+)\?=>i
  ENCODE   = "=\?%s\?B\?%s\?="
  def from
    if /^From:.*\n(\s+.*\n)*/i =~ self
      if MAILre =~ $&
        $&
      end
    end
  end
  def check_from
    read_addrs(MEMBERS).include?(self.from)
  end
  def del(hdr)
    self.gsub!(/^#{hdr}:.*\n(\s+.*\n)*/i,"")
  end
  def add(hdr,content)
    self << "#{hdr}: #{content}\n"
  end
  def subject(seq)
    unless self.sub!(/^Subject:\s*(.*)/i) do
        subj = $1.sub(SUBJre,"\\1 ")
        if ENCODEre =~ subj
          subj.sub!(ENCODEre) do |s; enc|
            enc = $1
            ENCODE % [enc, Base64.strict_encode64(
                           Base64.strict_decode64($2).sub(SUBJre,"\\1 "))]
          end
        end
        "Subject: "+(SUBJ % [seq, subj])
      end
      add("Subject", SUBJ % [seq, ""])
    end
  end
end

header, nl, body = STDIN.read.split(/(\r?\n){2}/,2)
header = Header.new(header << nl)

unless header.check_from
  not_member(header.from)
  exit
end

header.del("Received")
header.del("Reply-To")
header.add("Reply-To",MLADDR)
header.del("Sender")
header.add("Sender",OWNER)

seq = get_seq
header.subject(seq)

data = header+nl+body

Net::SMTP.start("localhost", 25, OWNER) do |smtp|
  smtp.send_message(data, OWNER, read_addrs(RECIPIENTS))
end

open(File.join(MAILDIR,seq.to_s),"w") { |f| f.write data }

手を抜いた点。

・Fromアドレスを抜き出す方法 (From: foo@example.jp <bar@example.jp> だとfooの方を見てしまう)

・Subject書き換えの時、1行目しか見ない

このあたりは、gemsを使えばちゃんと出来るのだが。


使い方としては、/etc/aliases に、

foo:        :include:/usr/local/lib/ml/foo
foo-owner:  管理者メールアドレス

と追記して、newaliases。

/usr/local/lib/ml/fooには、

"|/usr/local/bin/このスクリプト ; exit 0"

弱気ならば exit 0 を付けて、強気ならば付けない。デバッグ時はもちろん付けない)

と書いて、このファイルの所有者が /var/spool/ml/foo 以下を書けるようにディレクトリ作成して、

members と recipients と seq を記入。

トラックバック - http://d.hatena.ne.jp/otn/20141230

2014-12-24(水)

[] ISO-2022-JPメールが送れない(送りにくい)  ISO-2022-JPのメールが送れない(送りにくい)を含むブックマーク  ISO-2022-JPのメールが送れない(送りにくい)のブックマークコメント

標準添付ライブラリnet/smtp を使って ISO-2022-JPメールを送ろうとすると、エラー

require "net/smtp"

Net::SMTP.start("localhost", 25, "example.com") do |smtp|
  smtp.send_message(<<-EOS.encode(Encoding::ISO_2022_JP), "me@example.com", "to@example.net")
From: My Name <me@example.com>
To: Dest Address <to@example.net>
Subject: test mail

あいうえお
  EOS
end

#=> /usr/local/lib/ruby/2.1.0/net/protocol.rb:329:in `slice!':
 incompatible encoding regexp match (US-ASCII regexp with ISO-2022-JP string)
 (Encoding::CompatibilityError)

ISO-2022-JPハンドリング不自由があるのはしょうがないけど、ISO-2022-JPメールが送れないのはまずかろう。

  smtp.send_message(<<-EOS.encode(Encoding::ISO_2022_JP).force_encoding(Encoding::ASCII_8BIT), "me@example.com", "to@example.net")

とすれば送れる。

トラックバック - http://d.hatena.ne.jp/otn/20141224

2014-12-03(水)

[] Firefox 34.0で前回終了時のタブが復元されない  Firefox 34.0で前回終了時のタブが復元されないを含むブックマーク  Firefox 34.0で前回終了時のタブが復元されないのブックマークコメント

Firefoxを34.0にバージョンアップすると、原因不明でタブが復元されなくなった。復元はTab Mix Plus機能では無くて標準の機能で行っている。念のため、Tab Mix Plusの復元機能を使っても駄目。

会社PCでは問題ないので、プロファイルの原因だろうと言うことで、新規プロファイルを作ってみると復元される。

新規プロファイルに設定を移していこうかと思ったが、ググったときの情報で、「リセット」という方法があるのを発見

メニュー ⇒ ヘルプトラブルシューティング情報Firefox を初期状態にリセットする

ボタンを押すと、新規プロファイルを作って、ブックマーククッキーなどの問題無さそうな情報コピーしてくれる。

これを使って、あとはアドオンインストールアドオン設定ファイルコピーや設定のやり直しを行った。めんどくさい。


メニュー⇒ヘルプトラブルシューティング情報

というのは使ったこと無かったが、いろいろ便利な情報が載っているので今後は活用できそう。

アドオンファイル名(物によってUUIDになってしまうので、今までいちいち中を開いて何のアドオンか見ていた)

・変更された重要な設定(文字通り)

トラックバック - http://d.hatena.ne.jp/otn/20141203

2014-11-07(金)

[][] サーバー移行  サーバー移行を含むブックマーク  サーバー移行のブックマークコメント

http://otnx.jpサイトを、共用レンタルサーバーからVPSに移行した。

本日DNS の登録変更。


ようやくというか、移行しようと思ってから、2年半放っておいたのか。。。。。

あとまだメーリングリストの移行がある。

トラックバック - http://d.hatena.ne.jp/otn/20141107

2014-10-10(金)

[] 同じ名前ファイルリストアップ  同じ名前のファイルのリストアップを含むブックマーク  同じ名前のファイルのリストアップのブックマークコメント

テストしきってない気もするが、多分大丈夫そう。メモ

ファイル名にタブや改行が含まれない前提。

find /usr/local -type f -printf '%f\t%p\n' | sort | awk -F$'\t' '{if($1==A){if(A!=B)print X;print};B=A;A=$1;X=$0}'

順番入れ替えれば uniq で出来るかと思ったけど、無理。

2014-10-02(木)

[] @ITRuby入門記事の間違い  @ITのRuby入門記事の間違いを含むブックマーク  @ITのRuby入門記事の間違いのブックマークコメント

若手エンジニア/初心者のためのRuby 2.1入門(4):RubyのRangeクラスと範囲オブジェクト、範囲演算子、イテレーターの使い方 (1/3) - @IT

の内容間違いについて。


入門記事なんだから実装内部のことなど書く必要ないのに、書いて間違っている。


2ページ目の、「Rangeクラスのメソッド範囲内に存在するかを調べる」の部分。

趣旨としては、

・Range#include? は範囲内の要素と === で比較して一致するものがあるかどうかを調べている

・Range#cover? は範囲の両端と <=> で比較して範囲内にあるかどうか調べている

ということだが、少なくともMRIの include? については誤り。


Range#include? は両端が数値または長さ1の文字列の時は、両端と <=> 大小比較している。全要素を調べているわけじゃない。

それ以外のケースでは、親クラスの Enumerable#include? で処理している。ここで初めて一致するものが無ければ全要素がチェックされる。

ただし、そこでは == で各要素と一致を比較している。=== ではない。


Range#=== が Range#include? を使っていることと混同してしまって、こんなことを書いてしまったのではないか。


ついでに書くと、1ページ目の 補足「for文における範囲オブジェクトイテレーター」 は補足じゃなくて蛇足

forよりイテレータが好まれる主要理由が変数のスコープにあると誤解させかねない。このコラムはない方が良い。

トラックバック - http://d.hatena.ne.jp/otn/20141002

2014-09-24(水)

[] Google API文字コード  Google API と文字コードを含むブックマーク  Google API と文字コードのブックマークコメント

id:otn:20140901 の「MS Exchange/OutlookカレンダーGoogle Calendarの同期 」の続き。

ふと、Rubyデバッグモードで実行してみると、

Exception `ArgumentError' at D:/Ruby/lib/ruby/gems/2.0.0/gems/multi_json-1.10.1/lib/multi_json/adapter.rb:30
 - invalid byte sequence in Windows-31J

エラーが出ている。

      def blank?(input)
        input.nil? || /\A\s*\z/ === input
      rescue ArgumentError # invalid byte sequence in UTF-8
        false
      end

と、レスキューされているので実際にはエラーにはならないし、意味上も問題ないが、気になる。これは、inputのEncodingがWindows-31Jになっているが実際にはUTF-8の値が入っていると言うことだろう。JSON周りと言うことは、Google API周辺で出ているはずで、実際プログラムを削ってみると、Google APIリスト取得する段階でエラーになっていることがわかる。

Encoding.default_external = Encoding::UTF_8

を入れてみると、このエラーは出ない。ということは、Webから受け取ったデータのEncodingがデフォルトのままなので、Windows環境ではWindows-31Jと見なされてしまうというのが原因。default_external を変更できないケースもあるだろうからライブラリ側で何とかして欲しいところ。


ソースを見てみると、faradayライブラリが原因のよう。

require "faraday"
a = Faraday.new
u = %w|
  http://www.google.com/
  http://www.google.com/search?q=AAAAA
  http://www.google.co.jp/
|
u.each do |x|
  w = a.get(x)
  puts "#{w.status} #{w.body.encoding} #{w.headers['Content-Type']} #{x}"
end

==> 302 UTF-8 text/html; charset=UTF-8 http://www.google.com/
==> 200 ASCII-8BIT text/html; charset=ISO-8859-1 http://www.google.com/search?q=AAAAA
==> 200 ASCII-8BIT text/html; charset=Shift_JIS http://www.google.co.jp/

となる。他のURLも試してみると、200 だと ASCII-8BIT で、30x だと UTF-8 になるようだ。

charsetは見てない。faraday のソースgrep してもcharsetを気にしているらしい記述はない。


それではと思って、net/http を直接使って同様のプログラムを書いてみると、encoding は同様になる。

ということは、faraday は encoding については素通しで、そもそも、net/http で charset を見ずに ASCII-8BIT にしている。


しかし、これがどうして google-api-client を通すと Windows-31J になるのか?? default_external を変えると変わるし。

google-api-clientソースを見てもそれらしい記述はない。


とりあえずパッチを当てておく。

--- api_client.rb.bak   2014-08-11 23:35:44 +0900
+++ api_client.rb       2014-09-24 22:46:13 +0900
@@ -599,6 +599,7 @@

         case result.status
           when 200...300
+            result.body.force_encoding(Encoding::UTF_8)
             result
           when 301, 302, 303, 307
             request = generate_request(request.to_hash.merge({

2014-09-25追記

原因判明。

レスポンスが "Content-Encoding: gzip" の時に、zlib を使って伸張しているが、その際に default_external になってしまっているようだ。

伸張する前の encodig は ASCII-8BIT だ。

ということで、パッチを当てるなら、api_client/gzip.rb に当てるべき。

https://github.com/google/google-api-ruby-client/issues/160#issuecomment-67225979

トラックバック - http://d.hatena.ne.jp/otn/20140924

2014-09-05(金)

[] pry で文字色変更されず ESC とか表示される  pry で文字色変更されず ESC とか表示されるを含むブックマーク  pry で文字色変更されず ESC とか表示されるのブックマークコメント

gem の pry で、文字色が変わるべき表示で、ESC[31m などと ESC が英文字として表示されてしまうという現象が起きていた。

設定が何かおかしいだろうけど、色とか別にいいので、~/.pryrc で、Pry.config.color = false にして回避してた。


ふと、less が原因じゃないかと気づいた。

$ clear | less
ESC[HESC[J
$

やっぱり、ESC が "ESC" という文字に変換されている。


PAGER 環境変数を見ているところを探してみる。

$ grep -R -C5 PAGER .
./pager.rb-  # `SystemPager` buffers output until we're pretty sure it's at least a page
./pager.rb-  # long, then invokes an external pager and starts streaming output to it. If
./pager.rb-  # `#close` is called before then, it just prints out the buffered content.
./pager.rb-  class SystemPager < NullPager
./pager.rb-    def self.default_pager
./pager.rb:      pager = ENV["PAGER"] || ""
./pager.rb-
./pager.rb-      # Default to less, and make sure less is being passed the correct options
./pager.rb-      if pager.strip.empty? or pager =~ /^less\b/
./pager.rb-        pager = "less -R -F -X"
./pager.rb-      end

PAGER が、フルパスでない less のときに、-R オプション ( エスケープシーケンスをそのまま表示 )が設定されている。

現状の私の設定では、

PAGER=/usr/bin/less
LESS=-MXei~

なので、less が ESC を "ESC" という文字に変換して表示していたのが原因。


解決策としては、3通り考えられる。

・PAGER を設定しない

・PAGER=less にする

・LESS に -R を追加設定しておく

で、PAGER=less にすると色が付いた。

しかし、色分けってやっぱりいまいちなので、Pry.config.color = false のままにしておく。

普通の人で、問題が起こっていないのは、PAGER を設定していないせい?


あと、動作が不審なので、Pry.config.correct_indent = false も設定している。これが何者かまだ調べてないが。

トラックバック - http://d.hatena.ne.jp/otn/20140905

2014-09-03(水)

[] gvimフォントカーソル gvimのフォントとカーソル色を含むブックマーク  gvimのフォントとカーソル色のブックマークコメント

この手の設定は、%HOME%\_vimrc でなくて、%HOME%\_gvimrc に書かないといけないようだ。

_vimrc を _gvimrc にリネームして設定。

set directory=D:/var/Temp
set backupdir=D:/var/Temp
set noundofile
set ambiwidth=double
set incsearch smartcase nowrapscan viminfo+=h
set whichwrap= backspace=indent
set tabstop=4 shiftwidth=4
set guifont=MeiryoKe_Console:h12:cSHIFTJIS
highlight Cursor guibg=Black
autocmd FileType * setlocal formatoptions-=ro
トラックバック - http://d.hatena.ne.jp/otn/20140903

2014-09-01(月)

[][][] MS Exchange/OutlookカレンダーGoogle Calendarの同期 その4  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その4を含むブックマーク  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その4のブックマークコメント

仕様再掲。

3つめの仕様のために、Google Calendarに登録するときに、Exchangeからの反映であることをマークしないといけない。

使えそうな属性をみて、sourceという属性を使うことにする。ウェブページを元にイベントを登録するときにそのページのURLタイトルを書くようだ。

2014-09-19修正

コメントで、Googleのdescriptionは長さ上限*1があると教えていただいたので、Ecal#initializeで8192文字までに制限


全体のプログラム

#! ruby
CAL_ID = "xxxxxxxxxxxxxxxxx@gmail.com" #GOOGLEアカウントのメールアドレス
AUTH_FILE = "authfile.json"
SECRET_FILE = "client_secret.json"

ENV["http_proxy"] = "http://user:pass@proxyserver:port" # proxyの設定

Dir.chdir File.dirname($0)

require "google/api_client"
require "google/api_client/client_secrets"
require "google/api_client/auth/installed_app"
require "google/api_client/auth/file_storage"
require "win32ole"

class Event
  def to_s # デバッグ用に書いたもの
    [@start.strftime("%Y-%m-%d %H:%M"),
    @end.strftime("%Y-%m-%d %H:%M"),
    @allday.to_s[0],
    @reminder.to_s,
    @title, @body.gsub(/\s/," "), @location].join(",")
  end

  def ==(other)
    @start    == other.instance_variable_get(:@start)  and
    @end      == other.instance_variable_get(:@end)    and
    @allday   == other.instance_variable_get(:@allday) and
    reminder_eql?(@start, @reminder, other.instance_variable_get(:@reminder)) and
    @title    == other.instance_variable_get(:@title)  and
    @body     == other.instance_variable_get(:@body)   and
    @location == other.instance_variable_get(:@location)
  end

  def delete
    Gcal.execute(:delete,{"eventId" => @id})
  end

  def add
    event = {
      "summary" => @title,
      "description" => @body,
      "start" => start,
      "end" => ende,
      "location" => @location,
      "reminders" => reminder,
      "source" => {"title" => "Exchange", "url" => "http://localhost"}
    }
    Gcal.execute(:insert,
      {},
      :body => JSON.dump(event),
      :headers => {"Content-Type" => "application/json"}
    )
  end

  private
  def start
    if @allday
      {"date" => @start.strftime("%Y-%m-%d")}
    else
      {"dateTime" => @start.iso8601}
    end
  end
  def ende
    if @allday
      {"date" => @end.strftime("%Y-%m-%d")}
    else
      {"dateTime" => @end.iso8601}
    end
  end
  def reminder
    if @reminder
      {"useDefault" => false, "overrides"=>[{"method"=>"popup","minutes"=>@reminder}]}
    else
      {"useDefault" => false}
    end
  end
  def reminder_eql?(start, x, y)
    @@now ||= Time.now
    ( x == y ) or
    ( x and not y and start - x < @@now ) or # リマインダー時刻を過ぎている場合はリマインダーの有無を無視
    ( y and not x and start - y < @@now )
  end
end

# Googleカレンダー
class Gcal < Event
  def self.get_auth(calendar_id, auth_filename, secret_filename)
    @@cal_id = calendar_id
    @@client = Google::APIClient.new(:application_name => "")
    
    authfile = Google::APIClient::FileStorage.new(auth_filename)
    if authfile.authorization
      @@client.authorization = authfile.authorization
    else
      client_secrets = Google::APIClient::ClientSecrets.load(secret_filename)
      flow = Google::APIClient::InstalledAppFlow.new(
        :client_id => client_secrets.client_id,
        :client_secret => client_secrets.client_secret,
        :scope => ["https://www.googleapis.com/auth/calendar"]
      )
      @@client.authorization = flow.authorize(authfile)
    end
    @@service = @@client.discovered_api("calendar", "v3")
  end

  def self.execute(cmd, params, opt={})
    x = @@client.execute({:api_method => @@service.events.send(cmd),
      :parameters => params.merge("calendarId" => @@cal_id)}
      .merge(opt))
    if x.error?
      raise "#{x.status} #{x.error_message}"
    end
    x
  end

  def self.is_exchange(item)
    item.source and item.source.title == "Exchange"
  end

  def self.list(from, to)
    execute(:list,
         {"orderBy" => "startTime",
          "timeMin" => from.iso8601,
          "timeMax" => to.iso8601,
          "singleEvents" => "True"}
    ).data.items
  end

  def initialize(event)
    if event.start.date
      @start  = Time.parse(event.start.date)
      @end    = Time.parse(event.end.date)
      @allday = true
    else
      @start  = event.start.date_time
      @end    = event.end.date_time
      @allday = false
    end
    if event.reminders.use_default
      @reminder = 10 # 正確にはそのユーザーのデフォルト値を取得する必要がある
    elsif event.reminders.overrides[0]
      @reminder = event.reminders.overrides[0].minutes
    else
      @reminder = nil
    end
    @title = event.summary || ""
    @body  = event.description || ""
    @location = event.location || ""
    @id = event.id
  end
end

# Exchangeカレンダー
class Ecal < Event
  FolderCalendar = 9
  def self.list(from, to)
    @@calendar ||= WIN32OLE.new("Outlook.Application")
      .GetNamespace("MAPI").GetDefaultFolder(FolderCalendar)

    items = @@calendar.Items
    items.Sort "[Start]"
    items.IncludeRecurrences = true
    item = items.Find(%Q/[Start] < "#{to.strftime("%Y-%m-%d %H:%M")}" AND [End] >= "#{from.strftime("%Y-%m-%d %H:%M")}"/)

    Enumerator.new do |y|
      while item 
        y << item
        item = items.FindNext
      end
    end
  end

  def initialize(event)
    @start = event.Start
    @end   = event.End
    @allday = event.AllDayEvent
    @reminder = event.ReminderSet ? event.ReminderMinutesBeforeStart : nil
    @title  = event.Subject.encode(Encoding::UTF_8)
    @body   = event.Body.encode(Encoding::UTF_8)[0,8192]
    @location = event.Location.encode(Encoding::UTF_8)
  end
end

# 期間設定
today = Time.now
time_min = today - 3600*24*30
time_max = today + 3600*24*30

# Exchangeイベント取得
e_events = Ecal.list(time_min, time_max).map{|ev| Ecal.new(ev)}

# Google認証
Gcal.get_auth(CAL_ID, AUTH_FILE, SECRET_FILE)

# Googleイベント取得
g_events = Gcal.list(time_min, time_max)
  .select{|ev| Gcal.is_exchange(ev)}.map{|ev| Gcal.new(ev)}

# イベント削除
g_events.reject{|ev| e_events.include?(ev)}.map{|ev| ev.delete}

# イベント追加
e_events.reject{|ev| g_events.include?(ev)}.map{|ev| ev.add}

ハッシュキーシンボルでも良い場所もあるようだが、どうせ最後文字列になって送られるので文字列のままにしておく。

*1:改行を1文字として8192文字……バイトではない

kk 2014/09/17 22:09 ソース公開ありがとうございます。まったく同じ理由で困ってました。ありがたく使わせていただきます。
1点だけ。Outlook の Body のサイズは制限がないのですが、Google カレンダー の description は 8K byte (?) の制限があり、8K byte を超える部分は切り捨てられるようです。outlook 側のBodyが 8K byte 以上だった場合、比較が = にならずに、毎回 delete/add をしてしまいます。メールをそのまま、スケジュールとして登録することが多いので、たまにBodyが 8K を超えてしまいます。
とりあえずの quick hack で "active_support/core_ext/string/filters" を require して、181行目を
@body = event.Body.encode(Encoding::UTF_8)
==>
@body = event.Body.truncate(4096).encode(Encoding::UTF_8)
に修正して使っています。(Body/Description サイズを 4K に制限)
ご報告まで。

otnotn 2014/09/19 22:43 コメントありがとうございます。長い文章を入力してみましたが、8192文字(≠バイト、改行は1文字とカウント)のようですね。
まず大丈夫だと思いますが、本文にはメールを貼り付けることもあるので、越えることがないとも限りませんね。
修正しておきます。

2014-08-30(土)

[][][] MS Exchange/OutlookカレンダーGoogle Calendarの同期 その3  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その3を含むブックマーク  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その3のブックマークコメント

id:otn:20140829 の続き。


どういうときに、ExchangeイベントGoogle Calendarイベントを等しいと判断するか。

考慮必要なのは3点。

タイトル、内容、場所など文字列文字コードCP932か、utf-8かの違い。これはutf-8に揃えることにする。

また、内容がないときはExchangeは空文字列で、Google Calendarではnilで、これは空文字列に揃える。

Exchangeだと0時から翌日0時の予定で、全日フラグ(AllDayEvent)がtrueになっている。

Google Calendarだと、日付だけが文字列でセットされている。これはExchange式に揃える。

  • リマインダー

Exchangeは、リマインダー有無のフラグ(ReminderSet)と、分数がセットされている。また、リマインダー時刻が過ぎると、分数はそのままでフラグがfalseになる。

Google Calendarは、複数のリマインダーがセットできるようで配列になっている。また、リマインダーのデフォルト値があり、デフォルトかどうかのフラグ(use_default)がある。リマインダーがないときは、デフォルトフラグがfalseで配列が空。リマインダー時刻が過ぎても値は変化しない。


class Event
  def ==(other)
    @start    == other.instance_variable_get(:@start)  and
    @end      == other.instance_variable_get(:@end)    and
    @allday   == other.instance_variable_get(:@allday) and
    reminder_eql?(@start, @reminder, other.instance_variable_get(:@reminder)) and
    @title    == other.instance_variable_get(:@title)  and
    @body     == other.instance_variable_get(:@body)   and
    @location == other.instance_variable_get(:@location)
  end
  private
  def reminder_eql?(start, x, y)
    @@now ||= Time.now
    ( x == y ) or
    ( x and not y and start - x < @@now ) or # リマインダー時刻を過ぎている場合はリマインダーの有無を無視
    ( y and not x and start - y < @@now )
  end
end

class Gcal < Event
  def initialize(event)
    if event.start.date # 全日イベント
      @start  = Time.parse(event.start.date)
      @end    = Time.parse(event.end.date)
      @allday = true
    else
      @start  = event.start.date_time
      @end    = event.end.date_time
      @allday = false
    end
    if event.reminders.use_default
      @reminder = 10 # 正確にはそのユーザーのデフォルト値を取得する必要がある
    elsif event.reminders.overrides[0]
      @reminder = event.reminders.overrides[0].minutes
    else
      @reminder = nil
    end
    @title = event.summary || ""
    @body  = event.description || ""
    @location = event.location || ""
    @id = event.id
  end
end

class Ecal < Evant
  def initialize(event)
    @start = event.Start
    @end   = event.End
    @allday = event.AllDayEvent
    @reminder = event.ReminderSet ? event.ReminderMinutesBeforeStart : nil
    @title  = event.Subject.encode(Encoding::UTF_8)
    @body   = event.Body.encode(Encoding::UTF_8)
    @location = event.Location.encode(Encoding::UTF_8)
  end
end
トラックバック - http://d.hatena.ne.jp/otn/20140830

2014-08-29(金)

[][][] MS Exchange/OutlookカレンダーGoogle Calendarの同期 その2  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その2を含むブックマーク  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その2のブックマークコメント

id:otn:20140827 の続き。


Exchangeからデータ取得は以前調べてVBScriptで書いたものRubyで書き直す。

元のVBScriptを書くときに参考にしたサイトちょっと見つからない。


結果の取得は、FindNextしながらwhileループしていたので、Enumeratorに変換する。

class Ecal
  FolderCalendar = 9
  def self.list(from, to)
    @@calendar ||= WIN32OLE.new("Outlook.Application")
      .GetNamespace("MAPI").GetDefaultFolder(FolderCalendar)

    items = @@calendar.Items
    items.Sort "[Start]"
    items.IncludeRecurrences = true
    item = items.Find(%Q/[Start] < "#{to.strftime("%Y-%m-%d %H:%M")}" AND [End] >= "#{from.strftime("%Y-%m-%d %H:%M")}"/)

    Enumerator.new do |y|
      while item 
        y << item
        item = items.FindNext
      end
    end
  end
end

now = Time.now
time_min = now - 3600*24*7 #前後一週間
time_max = now + 3600*24*7
Ecal.list(time_min, time_max).each do |event|
  puts [event.Start, event.End, event.Subject].join(",")
end

strftimeの書式を調べると、"%Y-%m-%d %H:%M" は "%F %R" で良いが、覚えられないのでそのまま。

2014-08-27(水)

[][][] MS Exchange/OutlookカレンダーGoogle Calendarの同期 その1  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その1を含むブックマーク  MS Exchange/Outlook のカレンダーとGoogle Calendarの同期 その1のブックマークコメント

Google Calendar Syncのサービスが2014-08-01に停止された。今まで使っていたので、困ってしまい、代替を調べたが認証proxy越えがうまくいかないので、自分で作ることにした。


仕様としては、

ロジックとしては、これで良いはず。

  1. Exchangeの前後30日間のイベントを取得→リストE
  2. Google CalendarからExchange起因の前後30日間のイベントを取得→リストG
  3. リストGにあってリストEにないものを、Google Calendarから削除
  4. リストEにあってリストGにないものを、Google Calendarに追加

Exchangeイベント取得は前にやったことがあるので、まずはGoogle CalendarAPI確認

ググって、下記ページを参考にした。

http://qiita.com/iron-breaker/items/2440c4ab41a482b1b096

http://qiita.com/mechamogera/items/bf2ed20e332dc31d2352


プロジェクト作成

目的Googleアカウントログインしていることを確認した上で、管理コンソールページ https://cloud.google.com/consoleアクセス*1

[Create Project]ボタンを押す。

PROJECT NAME と PROJECT ID適当入力する。

I have read and agree to all Terms of Service for the Google Cloud Platform products.

にチェックを入れて、[Create]ボタンクリック

ここで、PROJECT IDGoogleシステム全体でユニークでないと、サーバーエラーになる*2

The Project ID specified is not available. Please select another.

These identifiers must be unique.

って、入力時に言ってくれよ!どうせ後で使わないようなので、おすすめに任せるのが吉。

PROJECT NAMEは、システム全体でユニークである必要はない。アカウント毎の管理


APIの設定

プロジェクトが出来たら、左サイドバーの、[API&auth]の[APIs]をクリック

Calendar API の Status が[OFF]になっているので、クリック

Terms of Serviceの確認ダイアログが出るので、チェックして[Accept]をクリック

1日に200,000リクエストまで使えるらしい。


クライアントIDの取得

サイドバーの[APIs]の下の[Credentials]をクリック

"Compute Engine and App Engine" 用のClient IDが出来ているが、これは関係ないので、

OAuthの下の[Create new Client ID]をクリック

APPLICATION TYPEは、"Installed application"を選択し、INSTALLED APPLICATION TYPEは、"Other"を選択。

[Create Client ID]をクリック

すると、"Client ID for native application"のClient IDが出来ている。

IDコピペする必要はなくて、[Download JSON]をクリックして、ダウンロードしておく。

ファイル名は、"client_secret.json" とかに縮めておく。

これで前準備は完了


疎通確認

確認のため、Googleカレンダーに何か登録しておく。

google-api-clientgemインストール

C> gem install google-api-client

イベントリストを取得して表示するだけのサンプルプログラム*3

require "google/api_client"
require "google/api_client/client_secrets"
require "google/api_client/auth/installed_app"
require "google/api_client/auth/file_storage"

ENV["http_proxy"] = "http://user:pass@proxyserver:port" # proxyの設定
client = Google::APIClient.new(application_name: "")

authfile = Google::APIClient::FileStorage.new("authfile.json") #このファイルはなくて良い。作られる
if authfile.authorization
  client.authorization = authfile.authorization
else
  client_secrets = Google::APIClient::ClientSecrets.load("client_secret.json") #ダウンロードしたJSONファイル

  flow = Google::APIClient::InstalledAppFlow.new(
    client_id: client_secrets.client_id,
    client_secret: client_secrets.client_secret,
    scope: ["https://www.googleapis.com/auth/calendar"]
  )
  client.authorization = flow.authorize(authfile)
end

service = client.discovered_api(:calendar, :v3)

now = Time.now
time_min = now - 3600*24*7 #前後一週間
time_max = now + 3600*24*7
params = {calendarId:   "xxxxxxxxxxxxxxxxx@gmail.com", #GOOGLEアカウントのメールアドレス
          orderBy:      "startTime",
          timeMax:      time_max.iso8601,
          timeMin:      time_min.iso8601,
          singleEvents: true}

result = client.execute(api_method: service.events.list,
                        parameters: params)
if result.error?
  puts "#{result.status} #{result.error_message}"
else
  result.data.items.each do |event|
    puts [event.start.date_time||event.start.date,
          event.end.date_time||event.end.date, event.summary].join(",")
  end
end

これを実行すると、ブラウザOAuth認証画面が出る。

「このアプリが次の許可をリクエストしています:」

と出るので、[承認]をクリック

さっき登録したイベントの日時とタイトルが出ればOK。

Googleアカウントがまちがっていると、404 Not Found が出る。


一度OAuth承認をしておくと、"authfile.json" に情報が保存され、以降はそれが使われる。

*1:左側の Account settings で Language を日本語にすることも出来るが、各種解説ページが英語ベースだったりするので、そのままにしておく

*2:Create Projectボタンが再び出なければ、左サイドバーの [Projects]というリンククリックする。あとは、[Create Project]ボタンからやり直し

*3google-api-client は faraday というライブラリ経由でウェブアクセスしているが、faraday は https 接続でも http_proxy を使うようだ

2014-08-25(月)

[] for /f の eol= オプション  for /f の eol= オプションを含むブックマーク  for /f の eol= オプションのブックマークコメント

今まで、for /f コマンドの eol= オプションについては無視してきた。これは、読み込んだ行の行頭の文字によって読み飛ばす機能だが、デフォルトの文字が「;」(セミコロンである。なので、昔作ったhead.batやtail.batでは、空行だけでなく行頭がセミコロンの行が飛ばされる。


これを無効にするのが難しい。

for /f "delims= eol=" %%A in (〜) do 〜

でいいと思ったのだが、これだと「"」(引用符)がeol文字になってしまう。かといって、

for /f "delims= eol= " %%A in (〜) do 〜

だと、「 」(空白)がeol文字になってしまう。^ とかを使ってもダメのようで、

for /f delims^=^ eol^= %%A in (〜) do 〜

と、引用符無しでオプションを書くとなんとかいけるようだ。


今までのスクリプトを全部これに書き直すかというと、ちょっとためらう。


delims= のデフォルトが空白なのはいいとしても、eol= のデフォルトは空でよかったのにと思うが。

2014-08-13(水)

[] gnu sed のアンドキュメンテッドな機能  gnu sed のアンドキュメンテッドな機能を含むブックマーク  gnu sed のアンドキュメンテッドな機能のブックマークコメント

sed の s コマンドに、man sed に書かれていない機能が色々あるようだ。

s/正規表現/置換文字列/オプションオプションで、

g … global(複数回置換)

p … print(置換後の文字列を表示)

i … ignore case(正規表現で英大文字小文字を区別しない)

は知られている。


$ for x in {0..9} {A..Z} {a..z};do echo -n $x:; echo a | sed s/a/b/$x; done

エラーにならない文字を調べてみる。


ということで、機能を調べると、

数字 … 指定番目のマッチのみ置換

数字g … 指定番目以降のマッチを置換

e … 置換後の文字列シェルコマンドとして実行してその出力で置換

m … 改行文字にマッチしない

w … 置換後の文字列ファイルに書き込む

がある。また、I と M はそれぞれ i と m と同じ。

$ echo {1..10} | sed 's/ /-/3'
1 2 3-4 5 6 7 8 9 10
$ echo {1..10} | sed 's/ /-/3g'
1 2 3-4-5-6-7-8-9-10

$ echo {1..10} | sed 's/ / + /g;s/.*/expr &/e'
55

$ seq 5 | sed 's/3/&&&/w/tmp/work'
1
2
333
4
5
$ cat /tmp/work
333

$ echo $'a\nb' | sed 'N;s/.../===/'
===
$ echo $'a\nb' | sed 'N;s/.../===/m'
a
b

数字のオプションは、man ed の s コマンドの所には書いてある。vim/ex の s コマンドでは使えないようだ。

m は、PerlRuby等の正規表現の m オプションとは逆だが、これは元々の sed機能が改行文字を対象にしていたからだろう。そういった意味では、大文字の M と書いた方が誤解されないだろう。そのために大文字も同じ機能だとすると、深い。

トラックバック - http://d.hatena.ne.jp/otn/20140813

2014-07-20(日)

[] Chocolatey、Rapid Environment Editor 等のツール  Chocolatey、Rapid Environment Editor 等のツールを含むブックマーク  Chocolatey、Rapid Environment Editor 等のツールのブックマークコメント

Windows - 開発者がSurfacePro3を買ったらまずやること」http://qiita.com/tanakh/items/a1b4705fb58ef3c89800 を読んで知る。


Chocolatey https://chocolatey.org/

というのは、モジュール管理ツールのようなインタフェースWindowsの各種ソフトインストールアップデートできる物。便利そうなので、インストールしてみたが、インストール済みのものは検知してくれないようだ。いまいち

C:\Windows\system32>choco list -localonly
パッケージが見つかりませんでした。

C:\Windows\system32>

ということで、「買ったらまず」ということなのか。買ったらまずインストールして各種ソフトをこれ経由でインストールするのならば便利だと思う。

ただ、すでにインストールした物の役に立たないかというと、そうでもない。

C:\Windows\system32>choco list | findstr /i dropbox
dropbox 2.10.2

C:\Windows\system32>

のように、ソフトサイトに行かなくても最新バージョンがいくつかを知ることが出来る。

C:\Windows\system32>choco list | wc -l
1984

C:\Windows\system32>

たくさん登録されているので、役立ちそう。。。。と思ったのだが、物によってはメンテされていないようで、

C:\Windows\system32>choco list | findstr /i adblock
adblockpluschrome 0.0.0.2
adblockplusfirefox 0.0.0.1
adblockplusie 1.1
adblockplusopera 0.0.0.1

C:\Windows\system32>

まあ、ブラウザプラグインについてこのツールを使う必要はないだろうけど、こういうのがリポジトリから削除されていないと言うことは、他の物も心配メジャーな物は大丈夫だろうけど。


Rapid Environment Editor http://www.rapidee.com/en/about

環境変数設定エディタで、ファイルパスフォルダパスなどをファイル選択ダイアログで指定できるので、タイプミス心配がない。

まあ、しかし、環境変数をいじるのも最初を除けば滅多にないので、これも「買ったらまず」か。

インストーラーを使わずにzipファイルダウンロード解凍して、そのフォルダに rapidee.ini というファイルを作っておくと起動してもレジストリを汚さないので、必要なときに使えればいいか。

なお、このツールで、下記のような存在しないフォルダを指している環境変数を見つけた。どうしたらいいのか。

configsetroot=%SystemRoot%\ConfigSetRoot

トラックバック - http://d.hatena.ne.jp/otn/20140720

2014-03-24(月)

[] find.exeバグ制限値?  find.exe のバグ?制限値?を含むブックマーク  find.exe のバグ?制限値?のブックマークコメント

find では長い行を扱えないようだ。

C:\>ruby -e "5.times{puts '@'*4094}" | find "@" | ruby -nle "puts $_.size"
4094
4094
4094
4094
4094
C:\>ruby -e "5.times{puts '@'*4095}" | find "@" | ruby -nle "puts $_.size"
20474
C:\>

改行を含んで4096までか?

findstr だと大丈夫

トラックバック - http://d.hatena.ne.jp/otn/20140324

2014-03-06(木)

[] 日本人の神はどこにいるか一神教多神教  日本人の神はどこにいるか 〜 一神教=多神教を含むブックマーク  日本人の神はどこにいるか 〜 一神教=多神教のブックマークコメント

久々に書き留めておこうと思う本を読んだ。

日本人の神はどこにいるか (ちくま新書)

日本人の神はどこにいるか (ちくま新書)

タイトルずばり日本人の神と言うより、一神教多神教について、純粋一神教は無いし純粋多神教も無いというのがテーマ


カトリックが、聖母信仰聖人信仰多神教的要素があるというのはよく見かけることだが、純粋一神教と思っていたイスラム聖者というのがあり、聖者に願い事をするというのがあるそうだ。もちろん、原理主義者はそれらを認めないが。


一方の日本において、様々な神・仏がいて、多神教に思える。しかし、具体的な現世利益を望んで神様を選んで参拝するときを除けば、多くのいやほとんどの日本人は、その神社祭神が何であるのか、その寺院本尊が何であるのか、意識しないで参拝しているというのは当たっている。初詣で明治神宮に集まる人に、「他のどの神でも無く明治天皇に参拝したいのか?」と問えばそうだと答える人は少ないだろうし、奈良の大仏に参拝する人が、みんな毘盧遮那仏とはいったいどういう仏様なのか理解しているとも思えない。

これは最近のことでも無いのだろう。伊勢神宮に参拝した西行法師の有名な言葉「何事のおはしますかは知らねども かたじけなさに涙こぼるる」がある。もちろん、彼は伊勢神宮祭神を知っていたはずだが、その上で「何事のおはしますかは知らねども」と言ったはず。

この、具体的な○○神でなく、抽象化された漠然とした神仏への信仰・畏れが、一神教における唯一神信仰に通じるのでは無いかというのがこの本のポイント

トラックバック - http://d.hatena.ne.jp/otn/20140306

2014-02-22(土)

[][] Yahoo検索結果から広告を削除するGreasemonkey  Yahoo検索結果から広告を削除するGreasemonkeyを含むブックマーク  Yahoo検索結果から広告を削除するGreasemonkeyのブックマークコメント

昨今の話題で、Yahoo検索結果の広告部分がマルウェア汚染されているようだ。

普段はGoogle検索を使ってるけど、今後絶対にYahoo検索を使わないとも言い切れず、そのときはきっと汚染を忘れていそうなので、Greasemonkeyを書いておく。

// ==UserScript==
// @name        Yahoo検索結果の広告削除
// @namespace   http://d.hatena.ne.jp/otn/
// @include     http://search.yahoo.co.jp/search?*
// @version     1
// @grant       none
// ==/UserScript==
var x;
x=document.getElementById("So1");
if(x)x.parentNode.removeChild(x);
x=document.getElementById("So2");
if(x)x.parentNode.removeChild(x);

二回くらいなので、ベタで書いた方がシンプルだと思うけど、繰り返しで書くとこうか。

["So1","So2"].forEach(function(id){
	var x=document.getElementById(id);
	if(x)x.parentNode.removeChild(x);
});
トラックバック - http://d.hatena.ne.jp/otn/20140222

2014-02-16(日)

[] bashcaseではまる  bashのcaseではまるを含むブックマーク  bashのcaseではまるのブックマークコメント

bashcaseで、括弧に囲まれた物にマッチさせたくて、

X="(X)"
case "$X" in
"(*)") echo OK ;;
*) echo NG ;;
esac

とやって失敗。

X="(X)"
case "$X" in
"("*")") echo OK ;;
*) echo NG ;;
esac

が正解。括弧はクォートが必要で、* はクォートしてはいけない。

トラックバック - http://d.hatena.ne.jp/otn/20140216