2015年1月17日土曜日

Google Apps Scriptのファイル/フォルダ操作(2)

同じパスのフォルダやファイルが複数存在可能なGoogleDriveで階層が深いフォルダを一発で指定したり、階層が深いフォルダを一回で作成するための関数を検討してみた。簡単な動作確認では思ったとおりに動作しているようだ。まったくオブジェクト志向ではないコードになってしまいました。
/**
* フォルダパスを指定してフォルダを生成する。
*
* @param {String} path フォルダパス
* @param {Folder} base ベースフォルダ(省略時はルート)
* @return {Folder} 生成したフォルダ。失敗時はnull。
*/
function createFolder(path,base)
{
if(typeof base === 'undefined'){
base = DriveApp.getRootFolder();
}
var splitpath = path.split("/");
if( splitpath[0] == "" ){
splitpath.splice(0,1);
}
if( splitpath[0] == "" ){
base = DriveApp.getRootFolder();
splitpath.splice(0,1);
}
var parent= new Array();
var child=new Array();
parent[0] = base;

for( var level=0; level < splitpath.length && splitpath[level]!=""; level++ ){
for( var i = 0; i<parent.length ; i++ ){
var folders = parent[i].getFoldersByName(splitpath[level]);
while( folders.hasNext() ){
child.push( folders.next() );
}
}
if( child.length == 0 ){
child[0] = parent[0].createFolder( splitpath[level] );
}
parent = [];
parent = [].concat(child);
child=[];
}
return parent[0];
}
/**
* フォルダパスを指定してフォルダを取得する。
*
* @param {String} path フォルダパス
* @param {Folder} base ベースフォルダ(省略時はルート)
* @return {Folder} ファイルパスを指定してファイルを取得する。失敗時はnull。
*/
function getFoldersByPathName(path,base)
{
if(typeof base === 'undefined'){
base = DriveApp.getRootFolder();
}
var splitpath = path.split("/");
if( splitpath[0] == "" ){
splitpath.splice(0,1);
}
if( splitpath[0] == "" ){
base = DriveApp.getRootFolder();
splitpath.splice(0,1);
}
var parent= new Array();
var child=new Array();
parent[0] = base;

for( var level=0; level < splitpath.length && splitpath[level]!=""; level++ ){
for( var i = 0; i<parent.length; i++ ){
var folders = parent[i].getFoldersByName(splitpath[level]);
while( folders.hasNext() ){
child.push( folders.next() );
}
}
if( child.length != 0 ){
parent = [];
parent = [].concat(child);
child=[];
}else{
return null;
}
}
return parent;
}

2015年1月12日月曜日

Google Apps Scriptのファイル/フォルダ操作(1)

GASでGoogle Driveのファイルにアクセスするときには少し癖がある。例えば、"ABC/DEF"フォルダにアクセスするために"DocsList.getFolder(path)"という関数が提供されており、"/"をセパレータとしてフォルダやファイルを指定することができる。
 var folder = DocsList.getFolder('ABC/DEF/GHI');
しかし、GASのReferenceによるとこの関数は"Deprecated. This function is deprecated and should not be used in new scripts."となっている。Deprecatedになった理由を考えてみるとGoogle Driveの癖のある仕様に行き着く。 Windows(DOS)やLinixなどではPATHでファイル/フォルダを識別しているが、Google Driveでは同じフォルダに同じ名前のファイルやフォルダを作成することができるため、PATHを指定しても一つのファイル/フォルダを特定できないのだ。 そのため以下の様な構成も可能なのであり、'ABC/DEF/GHI'と指定しても(1)〜(4)のどのフォルダなのか特定することができない。
ABC/
DEF/
GHI/ ・・・(1)
GHI/ ・・・(2)
DEF/
GHI/ ・・・(3)
GHI/ ・・・(4)
Google Driveで一つのファイルやフォルダを特定するのはIDであり、パスではないので注意が必要だ。例えば、スクリプトのログをひとつのファイルに保存し続けたい場合などは注意が必要だ。確実に同じファイルにアクセスするためには一度作成したファイルのIDを保存しておきそのIDを用いてアクセスしなければならない。
今後作成するスクリプトでログを残したくなることがあると思うので、まずはScript/Logフォルダにログファイルを保存する仕組みを考えていきたい。
 目標仕様としては
  • ログファイルIDをスクリプトのプロパティとして保存する。
  • 初回起動時などプロパティにログファイルIDが保存されていない時は指定されたパスに従いファイルを探す。
  • 指定されたパスのファイルが存在しなかった場合には新規ファイルを作成し、IDをプロパティに保存する。
  • 指定されたパスのファイルが存在しているときは最初に見つかったものを採用し、IDをプロパティに保存する。
注意事項としては以下の構成で"Script/Log/yyyLog.TXT"を見つけることだ。
(1)のScriptフォルダに入り、(2)のLogフォルダに入り、"yyyLog.TXT"を検索し見つからなかった場合、(2)のフォルダに"yyyLog.TXT"を作っていはいけない。(1)フォルダ下の検索が完了したら(3)フォルダ、(4)フォルダと検索しyyyLog.TXTを見つけなければならない。
Script/ ・・・(1)
Log/ ・・・(2)
Script/ ・・・(3)
Log/ ・・・(4)
yyyLog.TXT

仕様として考えなければいけないこととしては"yyyLog.txt"が存在しない時にどこにファイルを作成するかだ。
案1:最初に見つかったフォルダに作成
Script/ ・・・(1)
Log/ ・・・(2)
yyyLog.TXT
Script/ ・・・(3)
Log/ ・・・(4)
案2:最後に見つかったフォルダに作成
Script/ ・・・(1)
Log/ ・・・(2)
Script/ ・・・(3)
Log/ ・・・(4)
yyyLog.TXT
実際には同じフォルダに同じ名前のファイル/フォルダを作成することはないのだが、しっかり考えて見ると面倒なことに気がついた、、、

2015年1月5日月曜日

Chromebook(Acer C720) 購入

以前から興味があったChromebookを購入した。興味があったと言ってもどのメーカーからどのような機種が発売されているかなどは調べていなかったので、ヨドバシ・ドット・コムで適当に調べ、価格的にも安く、デザインも気に入ったAcer C720を32,180円で購入した。もしかしたらAmazonなどでもう少し安く買えるかもしれないが、調べないようにしている。
初回起動は時間がかかったが、起動完了後はGoogleのアカウントとパスワードを入力するだけですぐに使用できた。しかもパソコンで使用しているChromeブラウザのテーマやお気に入りなどが引き継がれておりすぐに使用できた。

DesktopパソコンにリモートデスクトップするのがChromebookを購入した目的の一つだったので、早々にChrome RDPをインストールして接続を試してみたが、つまらないくらい簡単に接続出来た。
後はWakeup-On-Lanのアプリケーションがあれば良いのだが、まだ見つかっていない。

iPhoneのテザリングも問題なく接続でき、L2TP/IPSecによるVPN接続も問題なく接続できた。リモートデスクトップ端末としては最適の選択肢だったと思う。


2012年4月23日月曜日

Toshiba Bluetooth StackでContinua

Toshiba Bluetooth StackでContinua対応機器との通信するソフト開発について調べて見た。どうやらContinua対応機器とBluetoothで通信するにはHDP (Health Device Profile)に対応している必要があるようだ。

Toshiba Bluetooth StackのVersionについて

Toshiba Bluetooth PortalのNewsページによるとVer.7.00.10でHDPに対応し、Ver.7.10.10 でCountinuaのロゴを取得している。

2010-03-19:First Continua Health Alliance certified Toshiba Bluetooth Stack version 7.10.10 released

2009-10-22:New Toshiba Bluetooth Stack v7.00.10 with Windows 7 and HDP profile support released
まずはToshiba Bluetooth StackのVer.7.10.10以降が必要そうなので、手持ちのPLANEX:BT-MicroEDR2Xについて調べてみた。Toshiba Bluetooth Stackを採用しているが、2009/09/04以降にドライバのアップデートがされていないし、マニュアルを見てもHDPに対応していないようなので、Countinuaのロゴを取得する前のバージョンのようだ。

いろいろ探したところ、Toshiba Bloutooth PortalのDownloadsページからVer.8.00.12をダウンロードできそうだが、「For TOSHIBA notebooks」となっているのでライセンス的に使用できない。(たぶんPLANEX:BT-MicroEDR2Xでも動くと思うが、大人なので我慢)

Bluetoothドングルについて

Toshiba Bluetooth Stackが付属していてVer.7.10.10以降のアップデータを公開しているドングルを購入する必要がある。最近のBluetoothドングル(特にBluetooth3.0対応)はToshiba以外のStackを採用していることが多いようだ。
「コンティニュア対応のBluetoothアダプター用最新ドライバー公開」という記事を発見したがToshibaスタックとは書かれていない。対応商品として以下の2つが紹介されていた。

  • Bluetooth2.1+EDR対応 USBアダプター(class1)BSHSBD02BK
  • Bluetooth2.1+EDR対応 USBアダプター(class2)BSHSBD03シリーズ
それぞれのダウンロードページ(BSHSBD02BKBSHSBD03)を確認したところContinua対応と書かれている。Stack更新日以降マニュアルが更新されていないので、最新のStackがToshiba製かどうか不明だが、古いマニュアル(BSHSBD02BKBSHSBD03)ではToshiba製Stackの画面を用いて説明をしているので、Toshiba製Stackが付属していると考えて問題がないだろう。市場在庫が少なくなってきているようなので今のうちに入手したほうがよい、いそいで探さないと!!
ついでにBluetooth3.0や4.0に対応しているBSHSBD04BK/BSHSBD05BK/BSHSBD08BKのマニュアルを確認したらToshiba製Stackではないようだ。

Toshiba Bluetooth Stack Software Development Kit(SDK)について

SDKの入手方法についてはToshiba Bloutooth PortalのKnowledgeページ「How to obtain the TOSHIBA Bluetooth stack SDK」に書かれている。SDKは無料だが"License Agreement for using SDK"に同意が必要、"License Agreement for using SDK"はhttps://aps2.toshiba-tro.de/bluetooth_sdk/Bluetooth.aspxからの申し込みが必要だとさ。めんどくさい、こんなんだからBluetoothはそれほどはやらないのか、、、、といっても東芝が得するビジネスモデルでもないからSDKをばら撒いても仕方がないのか?

とりあえず、申し込んでみたが回答待ちだ。

その他情報源

閥努 閻怒 の報われぬ日々でToshiba Bluetooth Stackについて書かれていた。


Bluetoothドングル、Ver.7.10.10以降のToshiba Bluetooth Stack、Toshiba Bluetooth Stack SDKがそろったらまたいろいろ調べよう。ところでSDKの開発環境はどうなっているのだろうか?Freeのコンパイラでも開発できるのか?

2012年4月7日土曜日

Nike+で異常なデータを探す方法

iPhoneのNike+GPSアプリやNike+ SportWatch GPSなどで記録し、Nike+でデータの管理を行っている。今年の初めに自己最高記録を確認したら0'05"/KMになっていた。1キロを5秒って、はやっ!!
Nike+ SportWatch GPSで記録した場合は精度よく記録されるが、iPhoneで記録した場合にはGPSの精度が悪いようだ。SportWatchは腕につけるのでGPSを捕らえやすいが、iPhoneはズボンのポケットに入れているのでGPSのを捕らえにくいのが原因かもしれない。
このGPSの精度が悪い異常なRUNデータを削除することで異常な自己最高記録を直せることは容易に想像できたが、異常なRUNデータを探す方法がわからなかった。Nike+のサポートフォームから問い合わせを行うと、4回程度メールをやり取りした結果、異常データの探し方を教わることができた。

(1)Nike+にログインする。
(2)RUNデータを表示する。
(3)ルート表示に切り替える。
(4)画面下方にあるSPLITSに異常なペースが無いことを確認する。
(5)ペース表示に切り替え、次の日に切り替える。
※ (3)~(5)を繰り返し異常なRUNデータを探す。


結果として、0'05"/KMと0'44"/KMというデータがあったのでRUNデータを削除することで自己最高記録を修正することができた。130個程度のRUNデータだったのですべてを確認できたが、もう少し多かったらさすがあきらめていたかもしれない。もう少し操作性を改善してほしいところだ。

問い合わせのやり取りの中で、サポートの方からNike+のアカウント名とパスワードを聞かれた、、、新鮮!?というかメールでアカウントとパスワードを同時に送信するという初体験だった。


2012年4月6日金曜日

勤務時間をGoogleカレンダーにメモするためのスクリプト

勤務時間を簡単にメモするためのgoogle apps scriptを作成してみた。サービスとして公開し出社時、退社時に実行すればよい。実行結果の表示はもう少し工夫が必要。
function doGet(e){

var now = new Date();
var begin = new Date(now);
begin.setHours(0);
begin.setMinutes(0);
begin.setSeconds(0);

// デフォルトのカレンダーを取得
var cal = CalendarApp.getDefaultCalendar();

var events = cal.getEvents( begin, now);
for(var i=0; i<events.length; i++){
if( events[i].getTitle() == "勤務中" ){
var start = events[i].getStartTime();
events[i].setTime( start, now );
return retComment(e,"勤務時間を更新しました。",start, now);
}
}
cal.createEvent("勤務中", now, now);
return retComment(e,"出勤登録をしました。",now, now);
}


function retComment(e,comment,start,end){
var app = UiApp.createApplication();
var panel = app.createVerticalPanel();
panel.add(app.createLabel(comment));
panel.add(app.createLabel(" "+start));
panel.add(app.createLabel(" "+end));
app.add(panel);
return app;
}

2010年11月29日月曜日

iPhoneからの投稿テスト

BloggerはSafariからも投稿可能だが、さすがに使いにくいので、専用アプリを試してみた。以下のアプリを候補に上げた、理由は、、、無料!

  • BlogWriterLite
  • Free mobile blogging with BlogBooster
  • BlogSpace

まず始めに、BlogWriterLiteを試してみたが、いきなり欠点が発覚した。編集途中でアプリを切り替えると編集内容がクリアされてしまう。アプリ上で下書き機能がある様だが、使い勝手は良くない。またサーバー上の下書きを編集する事が出来ない。もしかしたら出来るのかも知れないが、直感的に操作出来なかった。

次にBlogBoosterを試してみた。BlogWriterLiteで指摘した欠点は全てクリアしていた。強調イタリック下線取消線、画像アップロードなど出来る。フォント変更も出来そうだが、使い方が分からなかった。

次にBlogSpaceを試してみた。これもBlogWriterLiteで指摘した欠点は全てクリアしていた。しかし、BlogBoosterにあったhtmlのタグを挿入してくれる機能はない。

ここまで書いた時点BlogBoosterで決まりかと思ったが、BlogBoosterで作成した下書きをBlogSpaceで確認したら、勝手に以下のものが挿入されていた。

<span id='BB_SIGN_BEGIN'><img alt='BlogBooster-The most productive way for mobile blogging. BlogBooster is a multi-service blog editor for iPhone, Android, WebOs and your desktop' src='http://theblogbooster.com/pixel.gif' style='border:none;'/></span>


もう少し時間をかけて調査する必要がありそうだ。

2010年11月23日火曜日

Google Apps Script サービスとして公開

サービスとして公開してみる。
まずは以下のコードを作成する。

function doGet(e){
var app = UiApp.createApplication();
var panel = app.createVerticalPanel();
panel.add(app.createLabel("Hellow. World."));
app.add(panel);
return app;
}

あとはスクリプトエディタの「共有」=>「サービスとして公開...」を選択する。表示されたダイアログの「サービスを有効にする」にチェックをつける。あとはURLに表示されているアドレスにアクセスすると、以下のように表示される。
Hellow. World.

google apps scriptとは関係ないが、上記のように長いアドレスはgoo.glなどのサービスを用いて短縮するとよいかもしれない。

2010年11月18日木曜日

Google Apps Script 連絡先にアクセスしてみる

Googleの連絡先にアクセスするベーシックなスクリプトを作ってみた。連絡先に登録されているすべてのデータを取得し、spreadsheetsに書き出すだけである。


function myFunction() {
var contacts = ContactsApp.getAllContacts();

var doc = SpreadsheetApp.getActiveSpreadsheet();
var cell = doc.getRange('A1');
for (var i in contacts) {
var contact = contacts[i];
var FamilyName = contact.getFamilyName();
var GivenName = contact.getGivenName();

cell.offset(i, 0).setValue(FamilyName);
cell.offset(i, 1).setValue(GivenName);
}
}

2010年11月15日月曜日

Google Apps Script カレンダーイベント取得の動作

カレンダーイベントを取得したときの、取得順番を調べてみた。

var cal = CalendarApp.getDefaultCalendar();

var events = cal.getEvents( new Date("July 10, 2009 JST"), new Date("July 25, 2009 JST") );


上記のコードで取得したときにevents配列に取得できるイベントは以下の法則で決まっているようである。
しかし、イベントには多くのパラメータがあるので、ほかにもイベント取得順番に影響を与えているパラメータがあるかもしれない。
  1. イベント終了時刻が早いものから格納される

  2. イベント開始時刻が早いものから格納される。

  3. イベントタイトルの降順

簡単な調査の結果なので、まだまだ不確かな状態ではあるが、イベントの開始時刻よりも終了時刻が優先されているのが意外であった。