====================================================================
Jupyter NotebookのWebAPIを用いたファイルのアップロード／ダウンロード
====================================================================

.. Contents:: 目次
    :local:
    :depth: 2

概要
====

ここでは、Jupyter NotebookのWebAPIを用いたファイルのアップロード／ダウンロード方法を記載しています。

実行例・実装例のフォルダ構成
============================

実行例のフォルダ構成を下記に示します。
::

    / <Jupyter Notebookのルートディレクトリ>
    |- test_file
    |- test_folder
        |- test_folder_file_1.ipynb
        |- test_folder_file_2.ipynb

.. _example_explains:

リクエスト送信時に必要な値の取得
================================

* <Jupyter NotebookのURL>
    AAPF WebUIに表示されている[Access Jupyter Notebook] ボタンからJupyter Notebookにアクセスしてください。
    利用しているブラウザのアドレスバーに表示されているURLをコピーし、下記のように ``.../jupyter`` で
    終わるように修正した上で使用してください。

    ::

        http://web.example.com/proxy/a2b69b7d848324ee19b25278d29d45b4/jupyter

    このURLはAAPF WebAPIを利用して取得することもできます。詳しくは、
    *AAPF WebAPI Reference* を参照してください。

* <アップロード先・ダウンロード元のパス>
    Jupyter Notebook上のファイルパスを指定する際は、
    Jupyter Notebookのルートディレクトリからの相対パスを指定してください。

    下図の矢印が示すディレクトリがJupyter Notebookのルートディレクトリです。

    .. image:: ./_static/home_directory.png

    上記の画像の、 :file:`test_folder_file_1.ipynb` を指定するパスは以下となります。::

        /api/contents/test_folder/test_folder_file_1.ipynb

* <Jupyter Token>
    Jupyter TokenはAAPF WebUIから取得します。使用したいAAClusterの
    :guilabel:`[Access Jupyter Notebook]` ボタンをクリックし、 表示されるJupyter Tokenを使用してください。

    Jupyter TokenはAAPF WebAPIを利用して取得することもできます。詳しくは、
    *AAPF WebAPI Reference* を参照してください。

.. tip::

    使用方法の実行例には、Jupyter NotebookのWebAPI呼び出しに ``curl`` コマンドを使用しています。
    ``curl`` コマンドのインストールについては、 https://curl.haxx.se/download.html を参照してください。

ファイルのアップロード方法
==========================

25MB未満のファイルのアップロード
--------------------------------

25MB未満のファイルは ``PUT /api/contents/`` を使うことでアップロードできます。
ファイルの内容はリクエスト送信時にJSONパラメータの一部としてリクエストボディに入れます。

実行例
~~~~~~
ここでは ``bash`` を用いた実行例を記載しています。

.. code-block:: bash

    $ curl -X PUT \
        -H "Accept: application/json" \
        -H "Authorization: Token c1663431be5df1873505524d720f958aa1fecbfd9fc06123" \
        -d @- \
        "http://web.example.com/proxy/a2b69b7d848324ee19b25278d29d45b4/jupyter/api/contents/test_folder/upload_test.txt" << EOF
        {
            "content": "$(base64 upload_test.txt)",
            "type": "file",
            "format": "base64"
        }
        EOF

リクエストパラメータの指定方法
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Jupyter Tokenをリクエストヘッダーに設定します。

::

    -H "Authorization: Token <Jupyter Token>" \

リクエストボディとしてJSON形式のデータを送信します。内容は以下の構造で生成してください。

.. code-block:: json

    {
        "content": "$(base64 <アップロード元のパス>)",
        "type": "file",
        "format": "base64"
    }

* content
    Base64を使用してテキストにエンコードされたファイルの内容を設定します。
    この例では、 ``base64`` コマンドを使って <アップロード元のパス> にある
    ファイルの内容をエンコードしています。

アップロード先のパスをURLに含めて指定します。

::

    <Jupyter NotebookのURL>/api/contents/<アップロード先のパス>

.. note::

    アップロード時に存在しないディレクトリにファイルをアップロードすることはできません。
    アップロード先のディレクトリは事前に作成してください。


25MB以上のファイルのアップロード
--------------------------------

データ量が25MB以上のファイルは、1MBごとに分割してアップロードしてください。
また、この方法は25MB以下のファイルでもアップロードができます。

.. hint::

    ディレクトリをまとめてアップロードする場合は、 ``zip`` 等で圧縮したファイルをアップロードしてください。

実装例
~~~~~~

ここではPythonで実装した例を記載しています。

.. code-block:: py

    import requests
    import base64
    import json
    import os
    import sys

    # ファイルアップロード時に必要となる値を入れる変数です
    upload_from = 'upload_test.txt'
    jupyter_path = 'http://web.example.com/proxy/a2b69b7d848324ee19b25278d29d45b4/jupyter'
    jupyter_token = 'c1663431be5df1873505524d720f958aa1fecbfd9fc06123'
    upload_to = 'test_folder/upload_test.txt'

    with open(upload_from, 'rb') as file:

        file_name = os.path.basename(upload_from)
        chunk_size = 1024 * 1024

        headers = {'Content-Type': 'application/json',
                   'Authorization': 'token {}'.format(jupyter_token)}

        # ファイルを分割して読み込むメソッドです
        def divide_file():
            chunk = 0
            read_data_size = 0

            # 読み込んだファイルの容量がファイルサイズと一致するまで処理を実行させます
            content_length = os.path.getsize(upload_from)
            while read_data_size < content_length:
                content = file.read(chunk_size)
                encoded_content = base64.b64encode(content).decode('utf-8')

                # 読み込んだデータサイズの更新をします
                read_data_size = read_data_size + len(content)

                # ファイルが最後まで読み込まれた際に、`chunk`は`-1`とします
                if read_data_size == content_length:
                    chunk = -1
                else:
                    chunk += 1

                # リクエストのボディとなるように返却します
                yield {
                    'content': encoded_content,
                    'format': 'base64',
                    'name': file_name,
                    'path': upload_to,
                    'type': 'file',
                    'chunk': chunk
                }

        try:
            # ファイルを読み終わるまで繰り返す処理とします
            for payload in divide_file():
                # 作成したリクエストをJupyterに送信します
                requests.put('{}/api/contents/{}'.format(jupyter_path, upload_to),
                        headers=headers, data=json.dumps(payload))
        except:
            # 例外処理を記載します
            print("Unexpected error:", sys.exc_info()[0])
            raise

.. tip::

    Jupyter Notebookへリクエストを送信するツールとして、
    PyPIで公開されているパッケージの `requests <http://docs.python-requests.org/en/latest/#>`_
    を使用しています。

実装内容
~~~~~~~~

1. 送信するファイルを分割し、アップロードが可能な状態にします。

    1. ファイルを１MBごとに読み込みます。
    2. 1.で読み込んだファイルを ``Base64`` エンコードします。

2. 分割したファイルごとに ``PUT`` リクエストを送信します。

    1. リクエストごとに ``chunk`` という番号を ``1`` から順につけ、最後のリクエストには ``-1`` をつけます。
    2. ヘッダーに以下を設定します。
        ::

            {
                "Content-Type": "application/json",
                "Authorization": "token <Jupyter Token>"
            }

    3. リクエストボディの内容を生成します。
        ::

            {
                "content": <分割したファイルの内容>
                "format": "base64",
                "name": <ファイル名>,
                "path": <アップロード先のパス>,
                "type": "file",
                "chunk": <2-1. で付けた番号>
            }

    4. 下記のURLへ、 ``PUT`` で各リクエストを順に送信します。
        ::

            <Jupyter NotebookのURL>/api/contents/<アップロード先のパス>


ファイルのダウンロード方法
==========================

ファイルのダウンロードは ``GET /files/`` を使用します。

実行例
------

ここでは、bashを用いてダウンロードしたファイル内容を
リダイレクションにてファイル内へ書き込む例を、記載しています。

.. code-block:: bash

    $ curl -X GET \
        -H 'Accept: application/json' \
        -H 'X-Response-Encoding: chunked' \
        -H "Authorization: Token <Jupyter Token>" \
        "<Jupyter NotebookのURL>/files/<ダウンロード元のパス>" \
        > <ダウンロード先のパス>

リクエストパラメータ
--------------------

Jupyter Tokenをリクエストヘッダーに設定します。

::

    -H "Authorization: Token <Jupyter Token>" \

ダウンロード元のパスをURLに含めて指定します。

::

    <Jupyter NotebookのURL>/files/<ダウンロード元のパス>

.. hint::

    `GET /files/` では `Range` ヘッダーが使用可能です。このヘッダーを使用することで、
    ファイルの一部分をダウンロードしたり、ファイルダウンロードが失敗した際のリジュームを行うことができます。
