命名って難しい

変数、関数、クラスなどなど実装より命名に毎回悩むタイプの人間による技術や趣味についてのメモ。

C# - Chromeでダウンロードしているファイルの状況を確認するクラス

Seleniumからファイルのダウンロードリンクをクリックさせてダウンロードしたファイルをどうにかするプログラムの副産物

概要

Chromeはダウンロード中のファイルを以下の名前でダウンロードしていきます。

<元のファイル名>.crdownload

よって、そのcrdownloadファイルをうまいこと監視すればチェックしたいファイルのダウンロード完了を検出できるのです!
とか思ってたんですけど FileSystemWatcher って言う便利なクラスがあったんですね・・・。

もったいないので一応メモ。

ソースコード

    /// <summary>
    /// Chromeのダウンロードの状況クラス
    /// </summary>
    public class ChromeDownloadStatus
    {
        /// <summary>ダウンロードフォルダ</summary>
        public static readonly DirectoryInfo DownloadDirectory;

        /// <summary>ダウンロードフォルダの名前</summary>
        private static readonly string DownloadDirectoryName = "Downloads";
        /// <summary>キャッシュファイルの拡張子</summary>
        private static readonly string DownloadCacheExtension = ".crdownload";

        /// <summary>
        /// ダウンロード状態。
        /// </summary>
        public enum DownloadStatus
        {
            /// <summary>何もしていない</summary>
            None,
            /// <summary>ダウンロード中</summary>
            Downloading,
            /// <summary>ダウンロード完了</summary>
            Downloaded
        }

        /// <summary>
        /// 初期化。
        /// </summary>
        static ChromeDownloadStatus()
        {
            DownloadDirectory = new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), DownloadDirectoryName));
        }

        /// <summary>
        /// 対象のファイルのダウンロード状況を取得する。
        /// </summary>
        /// <param name="fileName">対象のファイル名</param>
        /// <returns></returns>
        public DownloadStatus GetDownloadStatus(string fileName)
        {
            var cacheFile = new FileInfo(Path.Combine(DownloadDirectory.FullName, fileName + DownloadCacheExtension));
            var file = new FileInfo(Path.Combine(DownloadDirectory.FullName, fileName));

            // キャッシュがある場合はダウンロード中
            if (cacheFile.Exists)
            {
                return DownloadStatus.Downloading;
            }
            // キャッシュがなくて、ファイルが存在する場合は完了
            if (!cacheFile.Exists && file.Exists)
            {
                return DownloadStatus.Downloaded;
            }
            return DownloadStatus.None;
        }

        /// <summary>
        /// ダウンロード完了まで待つ。
        /// タイムアウトの場合はNULLを返す。
        /// </summary>
        /// <param name="fileName">ファイル名(ダウンロードフォルダに存在するもの。</param>
        /// <param name="loopWaitSpan">チェックごとのWait</param>
        /// <param name="timeout">タイムアウト時間。</param>
        /// <returns>ダウンロード完了したファイル情報</returns>
        public FileInfo WaitUntilDownloaded(string fileName, TimeSpan loopWaitSpan, TimeSpan timeout)
        {
            var start = DateTime.Now;

            // 指定のファイル名のダウンロード状況を取得する
            Func<DownloadStatus> getStatus = () => GetDownloadStatus(fileName);
            // タイムアウトかどうか取得する。
            Func<bool> isTimeout = () => timeout < (DateTime.Now - start);

            var dlStatus = getStatus();

            // 既にDL終了であればそのままファイルを返す
            if (dlStatus == DownloadStatus.Downloaded)
            {
                return DownloadDirectory.GetFiles(fileName).First();
            }

            // ダウンロード開始直前の場合開始していない扱いになるため開始になるまで待つ
            if (dlStatus == DownloadStatus.None)
            {
                while (getStatus() == DownloadStatus.None)
                {
                    Thread.Sleep(loopWaitSpan);
                    if (isTimeout())
                    {
                        return null;
                    }
                }
            }

            // ダウンロード完了状態になるまで待つ
            while (getStatus() != DownloadStatus.Downloaded)
            {
                Thread.Sleep(loopWaitSpan);
                if (isTimeout())
                {
                    return null;
                }
            }

            // ここまで来たらDL完了のため、ファイルを取得する
            return DownloadDirectory.GetFiles(fileName).FirstOrDefault();
        }

    }

Powershellで複数のCSVファイルを特定の列で1つのファイルにマージ

概要

以下2つのCSVを共通の列名"HeaderA"でまとめたCSVにしたい!

A.csv

HeaderA,HeaderB
ValueA,ValueB

B.csv

HeaderA,HeaderC
ValueA,ValueC

以下Powershellスクリプトで実現。

get-childItem "*.csv" | foreach {
    Import-Csv -Path $_ | Select HeaderA | Export-Csv -Append -NoTypeInformation -Path AB.csv
}

備考/感想

  • Selectに存在しないヘッダーを選んでも大丈夫(無いものは空文字になる)
  • 結果ファイルの全部のデータがダブルクォーテーションで囲まれて出力される。注意。
  • もちろんヘッダー指定は複数でもOK("Select HeaderA, HeaderB"という感じ)

社内ホームページにRiot.jsを導入してみた質の極めて低いメモ

前提

環境

  • 社内のホームページはホームページビルダーなどで作った静的なページ。
  • ホームページサーバーはWinServerでIISの標準的な機能でホストしている。

実装者

  • web系開発経験なし。全然ノウハウなし。
  • ウェブサービスのフロントエンドとバックエンドがどうデータをやり取りしているのかも分からないレベル。

契機

社内のホームページにある社内連絡のtable要素を非エンジニアがhtmlを直接編集しており、記述に難儀していた。 直接編集する作業をやめたい、という要望があった。

導入

全体像

  • 社内連絡の内容を記載し、マクロによってjson出力するExcelマクロ有効ブックを作る…①
  • 非エンジニアに①を使用させ、出力を行う…②
  • ホームページで②を読み込ませ、それを元にRiot.jsで特定のタグをマウントする。

簡単ですね。実際はVBAが実装面倒すぎて辛かったんですけど。

実装

jsonファイル

トップの"renraku"から、"date","text","url"の3つのデータを配列で持たせています。

{  
   "renraku":[  
      {  
         "date":"2016/11/30",
         "text":"おしらせ~",
         "url":"http://google.com"
      },
      {  
         "date":"2016/11/29",
         "text":"ファイルへのリンクお知らせ",
         "url":"files/お知らせ.xlsx"
      }
   ]
}

社内連絡のページHTML

カスタムタグではなくID指定で試してみた。

<!doctype html>
<html lang="ja">

<head>
  <title>社内連絡</title>
</head>

<body>
  <div id="riot-renraku-table"></div>
  <script type="text/javascript" src="js/jquery-1.8.3.min.js"></script>
  <script type="riot/tag" src="tag/renraku-table.tag"></script>
  <script type="text/javascript" src="js/riot_compiler.min.js"></script>
  <script type="text/javascript">
    $.ajax({
      url: 'json/renraku.json',
      dataType: 'json',
      async: true,
      cache: false,
      success: function(res) {
        riot.mount('#riot-renraku-table', 'renraku-table', res.renraku);
      }
    });
  </script>
</body>

</html>

カスタムタグ

if文を使って、URLがあればa要素、なければdivという表示をしています。

<renraku-table>
  <table>
    <thead>
      <tr>
        <th>日付</th>
        <th>お知らせ</th>
      </tr>
    </thead>
    <tbody>
      <tr each={opts}>
        <td>{date}</td>
        <td>
            <a if={url!==""} href="{url}" target="_blank">{text}</a>
            <div if={url===""}>{text}</div>
        </td>
      </tr>
    </tbody>
  </table>
</renraku-table>

感想

なんか書くのが面倒になってしまったので感想でまとめて終わらそうと思うんですけど、
「なんかよくわからないが動いた」の一言につきます。

とりあえず初心者の手習いだと思っていただければ。

VBScriptで古い形式のExcelを新しい形式に変換する。

いちいち開いて名前を付けて保存するの面倒だよね、という事からつくりました。
変換したいファイル達をドラッグ&ドロップするだけで同ディレクトリに保存されます。

コード

' 定数
Const XlFileFormat_xlOpenXMLWorkbook = 51 ' .xlsx : Excel ブック

' ドラッグドロップで渡されたファイルを文字列で取り込む。メッセージ用。
set args = WScript.Arguments
fileList = ""
for each arg in args
  fileList = fileList & vbNewLine & arg
next

' 引数のチェック。対象ファイル以外が混ざっている場合終了。
set fobj = CreateObject("Scripting.FileSystemObject")
for each arg in args
    ext = fobj.GetextensionName(arg)
    if ext <> "xls" then
        msgbox "xlsファイル以外が指定されました。終了します。" & vbNewLine & fileList
        WScript.Quit
    end if
next

' 引数で貰った各Excelファイルを最新の形式で保存する。
set oXlsApp = CreateObject("Excel.Application")
for each path in args
    oXlsApp.Application.Visible = true
    set book = oXlsApp.Application.Workbooks.Open(path)
    book.SaveAs Replace(path, ".xls", ".xlsx"), XlFileFormat_xlOpenXMLWorkbook
    book.Close
next
oXlsApp.Quit
set oXlsApp = nothing

msgbox "変換完了しました。"

Powershell版つくりました(2017/02/03更新)

notshown.hatenablog.jp

Windows Server 2008 R2 の PowerShellをアップデートした。

方法

下記URLを参照し、.Net FrameworkWindows Windows Management Framework 4.0をインストールするだけ。

Step by Step Upgrading the Powershell Version 4 on 2008 R2 - TechNet Articles - United States (English) - TechNet Wiki

気をつけないといけないのはWindows Updateが適用されていないと接続されないこと。

ローカルサーバーだからアップデート切っていたのを失念していて時間を食ってしまった。

Powershellでタスクスケジューラのタスクをまとめてエクスポートするスクリプト

サーバーに多数タスクが登録されているのですが、 何かあった時のためにタスクをエクスポートし、バックアップしようと思い作成。

前提

以下の環境を前提とします。

  • Scheduled Tasks Cmdletsが使える

ソースコード

$taskBasePath = "\MyTask\*"
$taskSaveDir = "C:\MyTaskBackup"

Get-ScheduledTask -TaskPath $taskBasePath  | foreach {
    $taskDir = Join-Path $taskSaveDir  $_.TaskPath
    if( -not (Test-Path -Path $taskDir)) {
        mkdir -Path $taskDir
    }
    $path = (Join-Path $taskDir "$($_.TaskName).xml")
    Export-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath | Out-File $path # -WhatIf
}

解説

使う時書き換える変数については以下表を参照。

変数 用途 備考
taskBasePath タスクスケジューラの特定のパス。 このフォルダにまとめて定義しておけばよいという運用をしてます。
taskSaveDir 保存先のパス。 ここをSVNやGITのリポジトリに指定して定期的に自動コミット/プッシュしたりしたい。

動作としては 指定のパスのタスクを、格納しているフォルダの中に、そのタスク名で保存する という感じです。

無いフォルダは勝手に作ります。

既存のタスクを削除したりはしないので何度でも実行可能。

参考

Scheduled Tasks Cmdlets in Windows PowerShell

以上!

チートシート的メモ

概要

1,2行のちょっとしたコードをまとめる。
ちょいちょい更新していく。

PowerShell

現在のスクリプトディレクトリを取得する。

Split-Path $MyInvocation.MyCommand.Path
# pushd %~dp0と同じ処理
Push-Location -Path (Split-Path $MyInvocation.MyCommand.Path)

バッチ(コマンドプロンプト)

BCPで[-w]オプションつけて出力するとforで読めない

単純な文字列型[-c]にしよう。

javascript

getElementsByTagNameの結果をforEachする

Array.prototype.slice.callを使って配列に変換する必要がある。

Array.prototype.slice.call(document.getElementsByTagName("table")).forEach...