BITSをコードから使う

BITSの何よりすばらしいことは、ダウンロードのジョブを仕掛けたプログラムは、後の処理をBITSに任せて、さっさと終了してしまって良いことです。

どっかのアホ会社の書いたコードの様に独自のアップデータが常駐するような、ユーザーのCPUパワーとメモリを奪うだけのために存在するようなコードを書くぐらいなら、BITSを使うことを検討するべきでしょう。

さて、このサービスは、一般的には、ソフトウェアやデータのアップデートをダウンロードすることに利用されることが想定されているのですが、その利用用途のために、実は、ダウンロード完了後に特定のプログラムを実行するという機能まで付いています。

つまり、update.exeをダウンロード語にupdate.exeを実行することも出来るということです。

下記のコードは、ダウンロードしたファイルを実行したりはしませんが、ダウンロード完了時に自分自身を実行し、ダウンロード完了のメッセージを表示します。

使い方は、

bitswget http://www.example.com/foo/bar/hoge.zip hoge.zip

のような感じです。注意点としては、BITSは、ダウンロードに、HTTP/1.1のByte-Rangeを利用するので、それが利用できない場合には、エラーになってしまいます。また、このコードは、URLの妥当性などについては一切、検証を行っていませんし、ダウンロードのセッションを監視もしていないので、ダウンロードが失敗した場合には、ジョブが残ってしまいます。その場合には、bitsadmin /cancelで残ったジョブを消す必要がありますので注意してください。

// bitswget.cpp
#define UNICODE
#define _WIN32_WINNT  0x0500

#include <cstdio>
#include <clocale>
#include <exception>

#include <windows.h>
#include <bits.h>

#include <atlbase.h>
#include <atlstr.h>
#include <atlcom.h>

using namespace std;

// COMエラーをラッピングするクラス
class CComError : public exception
{
public:
  CComError(HRESULT hr, const char* expr, const char* file, int line)
  {
    m_message.Format("%s(%d): 0x%08X: %s", file, line, hr, expr);
    OutputDebugStringA(m_message + "\r\n");
    m_hr = hr;
  }

  virtual const char *what() const
  {
    return m_message;
  }

  HRESULT getHResult() const {return m_hr;}

private:
  CStringA m_message;
  HRESULT m_hr;
};

// FAILED(hr)時に例外を発生 TOF: Throw-On-Failure
#define TOF(expr) do {HRESULT hr = expr; if(FAILED(hr)) throw new CComError(hr, #expr, __FILE__, __LINE__); } while(0)

// CoTaskMemFree用
template<typename T> class CTaskMem
{
public:
  CTaskMem() : m_mem(NULL) {}
  ~CTaskMem() {release();}
  operator T*&() {return m_mem;}
  T** operator&() {return &m_mem;}
  void release()
  {
    if(m_mem)
    {
      CoTaskMemFree(m_mem);
      m_mem = NULL;
    }
  }

private:
  T* m_mem;

  // cannot copy
  CTaskMem(const T*);
  CTaskMem(const CTaskMem&);
  CTaskMem& operator=(const CTaskMem&);
};

// 本体
int wmain(int argc, wchar_t* argv[])
{
  std::setlocale(LC_ALL, "");
  CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

  if(argc < 3)
  {
    printf("bitstest URL LOCALFILENAME\n");
    return 0;
  }

  try
  {
    CComPtr<IBackgroundCopyManager> pMgr;
    TOF(CoCreateInstance(
      __uuidof(BackgroundCopyManager), NULL, CLSCTX_LOCAL_SERVER,
      __uuidof(IBackgroundCopyManager), (void**)&pMgr));


    if(argc == 3 && wcscmp(argv[1], L"--complete") == 0)
    {
      // ジョブを完了させる
      GUID jobId;
      CComPtr<IBackgroundCopyJob> pJob;
      IIDFromString(argv[2], &jobId);
      TOF(pMgr->GetJob(jobId, &pJob));
      pJob->Complete();

      // ついでにユーザーにメッセージを表示してみる
      CStringW msg;
      msg.Format(L"ダウンロードジョブ %s は正常に完了しました。", argv[2]);
      MessageBox(NULL, msg, L"ダウンロード完了", MB_OK | MB_ICONINFORMATION | MB_SYSTEMMODAL);
    }
    else
    {
      // 新しいジョブを作る
      GUID jobId;
      CComPtr<IBackgroundCopyJob> pJob;
      TOF(pMgr->CreateJob(L"bitswget", BG_JOB_TYPE_DOWNLOAD, &jobId, &pJob));

      CComQIPtr<IBackgroundCopyJob2> pJob2 = pJob;
      
      // queue a file
      WCHAR fileName[MAX_PATH];
      GetFullPathName(argv[2], MAX_PATH, fileName, NULL);
      TOF(pJob->AddFile(argv[1], fileName));

      // ダウンロード完了時に自分自身を --complete JOBID という
      // コマンドラインで呼び出すように設定する
      WCHAR selfExeName[MAX_PATH];
      GetModuleFileName(NULL, selfExeName, MAX_PATH);
      CTaskMem<WCHAR> guidstr;
      StringFromIID(jobId, &guidstr);
      CStringW params;
      params.Format(L"\"%s\" --complete %s", selfExeName, (LPCWSTR)guidstr);
      TOF(pJob2->SetNotifyCmdLine(selfExeName, params));

      // fire!
      TOF(pJob->Resume());
    }
  }
  catch(CComError& e)
  {
    printf("%s\n", e.what());

    CoUninitialize();
    return (int)e.getHResult();
  }
  CoUninitialize();
  return 0;
}