web-dev-qa-db-ja.com

ユーザーがファイルのダウンロードを受け入れるタイミングを検出する

ユーザーがファイルをダウンロードできるようにするハイパーリンクをクリックした後、ユーザーを別のWebページにリダイレクトしたい。ただし、ファイルを開く/保存するダイアログで選択する必要があるため、ダウンロードを受け入れるまでリダイレクトしたくありません。

彼らがこのアクションを実行したことをどのように検出できますか?

13
yitzih

私が長年維持してきた download.js を見つけたので、JSからを伝える方法がない(または可能性が高い)一般に、以下を参照してください)ユーザーがダウンロードの[開く/保存]ダイアログで行うことを選択します。これは一般的な機能のリクエストであり、長年にわたって繰り返し調査してきました。それは不可能だと私は自信を持って言える。誰かがファイルに対するプロンプト後のユーザーアクションを決定するための機械的な方法をデモできる場合は、この賞金の10倍を喜んで支払います!

さらに、それは実際にはJSルールの問題だけではなく、ブラウザーがそのようなファイルをダウンロードしてプロンプトする方法によって問題が複雑になります。これは、サーバーでさえ、何が起こったかを常に知ることができないことを意味します。いくつかの特定のケースにはいくつかの特定の回避策があるかもしれませんが、それらはかなりまたは単純ではありません。

ユーザーに<input type=file>を使用してダウンロードしたファイルを「再アップロード」して検証するように強制することもできますが、これはせいぜい面倒であり、ローカルファイルの参照ダイアログが気になる場合があります。ダウンロードを確実にする唯一の確実な方法ですが、機密性の低いアプリケーションの場合は非常に厳格であり、ファイルのサポートがない一部の「モバイル」プラットフォームでは機能しません。

また、サーバー側から監視して、ファイルがサーバーでヒットしたというメッセージをクライアントにプッシュすることもできます。ここでの問題は、バックグラウンドでは見えませんが、[開く/保存]ダイアログが表示されるとすぐにダウンロードが開始されることです。そのため、大きなファイルを「受け入れる」までしばらく待つと、最初はすぐにダウンロードされるように見えます。サーバーの観点からは、ユーザーの操作に関係なく、アクティビティは同じです。

巨大なファイルの場合、おそらくファイル全体が転送されなかったことを検出できます。これは、ユーザーが[キャンセル]をクリックしたことを意味しますが、これは複雑な同期手順ですステータスをバックエンドからクライアントにプッシュします。ソケット、PHPメッセージの受け渡し、EventSourceなど)を使用した多くのカスタムプログラミングが少しの利益のために必要になります。また、時間との競争であり、その時間は不確かであり、速度が低下します。ユーザーの満足のためにダウンロードは推奨されません。

小さなファイルの場合、ユーザーがダイアログボックスを表示する前に物理的にダウンロードされるため、サーバーは役に立たなくなります。また、一部のダウンロードマネージャー拡張機能がこの仕事を引き継ぐことも考慮してください。これらの拡張機能は、Vanillaブラウザーと同じように動作することが保証されていません。ダウンロードを「永久」に「終了」するのに時間がかかる低速のハードドライブを使用している場合、待機を強制することは危険です。私たち全員がこれを経験しており、「急な」風が吹いている間は継続できないため、ユーザーの満足度が低下し、穏やかに言えます。

簡単に言うと、簡単な方法はなく、一般的にはまったく方法がありません。ただし、ダウンロードに長い時間がかかることがわかっている巨大なファイルを除きます。私は私のdownload.jsユーザーに能力を提供するために多くの血汗と涙を費やしてきましたが、単に良い選択肢はありません。 Ryan dahlは最初にnode.jsを作成して、ユーザーにアップロードの進行状況バーを提供できるようにしました。おそらく誰かがサーバー/クライアントパッケージを作成して、ダウンロードでも同じことができるようにするでしょう。

11
dandavis

@dandavis

誰かがファイルに対するプロンプト後のユーザーアクションを決定するための機械的な方法をデモできる場合は、この賞金の10倍を喜んで支払います!

準備はできたか?ここでcuzは1つのハックなソリューションです????

StreamSaver libはa[download]でファイルをダウンロードするのにblobを使用しません。 Service Workerを使用して、サーバーがcontent-disposition添付ファイルヘッダーでダウンロードを処理する方法をエミュレートすることにより、何かをディスクにストリーミングします。

evt.respondWith(
  new Response(
    new ReadableStream({...})
  )
)

これで、ユーザーがダイアログで何を押したかを正確に知る方法はありませんが、ストリームに関するいくつかの情報があります。
ユーザーがダイアログで[キャンセル]を押すか、進行中のダウンロードを中止すると、ストリームも中止されます。

保存ボタンはトリッキーです。しかし、ストリームバケットhighWaterMarkが教えてくれることから始めましょう。

私の 急流の例 では、writer.desiredSizeをログに記録しています。それは、どれだけの量のデータを受け取りたいかという相関関係です。ストリームに何かを書き込むと、希望のサイズが減少します(カウントまたはバイト戦略に関係なく)。増加しない場合は、ユーザーがダウンロードを一時停止している可能性があります。 0未満になると、ユーザーが求めているよりも多くのデータが書き込まれます。

そして、あなたが行うすべてのチャンク書き込みは約束を返します

writer.getWriter().write(uint8).then(() => {
  // Chunk have been sent to the destination bucket
  // and desiredSize increase again 
})

バケットがいっぱいにならない場合、その約束は解決されます。しかし、それはチャンクがディスクにまだ書き込まれていることを意味するのではなく、チャンクが1つのストリームから別のストリームに渡されたことを意味します(書き込み->読み取り可能->応答から)。ストリームの別の以前のチャンクがディスクに書き込まれたとき。

ホールデータがバケット(メモリ)内に収まるかどうかをユーザーが選択する前でも、書き込みストリームが終了する可能性があります

バケットサイズを調整してデータよりも小さくする

したがって、ダウンロードの開始、終了、一時停止のタイミングを推測できますが、イベントを取得しないため、確実にわかりません(ストリームを閉じるアボートは別として)。

転送可能なストリーム のサポートがない場合、トレントの例では正しいサイズが表示されませんが、サービスワーカーの内部ですべてを行う場合は、この周りに。 (メインスレッドで行う代わりに)

ストリームの終了を簡単に検出する

readableStream.pipeTo(fileStream).then(done)

そして、将来の参考のために WICG/native-file-system は、ファイルをディスクに書き込むためのアクセス権を与える可能性がありますが、続行する前にプロンプ​​トダイアログの約束を解決する必要があり、ユーザーが求めているものである可能性があります

blob をストリームとして保存する例があり、さらに興味がある場合は 複数のblobをZipとして を保存する例もあります。

2
Endless

ユーザーがそうであるか、またはプロセスの次のステップの前にファイルをダウンロードする必要があることを知っている必要がある場合、ユーザーは、ファイルがダウンロードされたことの何らかの形の確認が行われることを期待する必要があります。

変更されたファイル名に設定されたdownload属性で<a>要素を使用することにより、ダウンロードされたファイル名に含める一意の識別子またはタイムスタンプを作成できます。

<button>要素のclickイベントで、<a>要素に対して.click()を呼び出し、hrefをファイルのBlob URLに設定します。 aエレメントでclickハンドラーが<input type="file">エレメントで.click()を呼び出します。ここで、添付されたchangeイベントで、ユーザーはファイルのダウンロードを開始したユーザーアクションでダウンロードされた同じファイルを選択する必要があります。

ユーザーアクションから始まる.click()の呼び出しのチェーンに注意してください。 トリガーをクリックしてinput = fileを非同期ajax done()でクリック を参照してください。

ユーザーのファイルシステムから選択されたファイルが変更されたダウンロードファイル名と等しい場合は、関数を呼び出します。それ以外の場合は、ファイルのダウンロードが確認されていないことをユーザーに通知します。

window.addEventListener("load", function() {
  
  let id, filename, url, file; 
  let confirmed = false;
  const a = document.querySelector("a");
  const button = document.querySelector("button");
  const confirm = document.querySelector("input[type=file]");
  const label = document.querySelector("label");

  function confirmDownload(filename) {
    if (confirmed) {
      filename = filename.replace(/(-\d+)/, "");
      label.innerHTML = "download of " + filename + " confirmed";
    } else {
      confirmed = false;
      label.innerHTML = "download not confirmed";
    }
    URL.revokeObjectURL(url);
    id = url = filename = void 0;
    if (!file.isClosed) {
      file.close()
    }
  }

  function handleAnchor(event) {
    confirm.click();
    label.innerHTML = "";
    confirm.value = "";
    window.addEventListener("focus", handleCancelledDownloadConfirmation);
  }

  function handleFile(event) {
    if (confirm.files.length && confirm.files[0].name === filename) {
      confirmed = true;      
    } else {
      confirmed = false;
    }
    confirmDownload(filename);
  }

  function handleDownload(event) {
    // file
    file = new File(["abc"], "file.txt", {
      type: "text/plain",
      lastModified: new Date().getTime()
    });
    id = new Date().getTime();
    filename = file.name.match(/[^.]+/g);
    filename = filename.slice(0, filename.length - 1).join("")
               .concat("-", id, ".", filename[filename.length - 1]);
    file = new File([file], filename, {
      type: file.type,
      lastModified: id
    });
    a.download = filename;
    url = URL.createObjectURL(file);
    a.href = url;
    alert("confirm download after saving file");
    a.click();
  }
  
  function handleCancelledDownloadConfirmation(event) {
    if (confirmed === false && !confirm.files.length) {
      confirmDownload(filename);
    }
    window.removeEventListener("focus", handleCancelledDownloadConfirmation);
  }

  a.addEventListener("click", handleAnchor);

  confirm.addEventListener("change", handleFile);

  button.addEventListener("click", handleDownload);

});
<button>download file</button>
<a hidden>download file</a>
<input type="file" hidden/>
<label></label>

plnkr http://plnkr.co/edit/9NmyiiQu2xthIva7IA3v?p=preview

1
guest271314

最近手を出して、ユーザーが特定の種類のファイルをアップロードできるかどうかを指定する必要のあるプロジェクトがありました(つまり、ユーザーはpngをアップロードできますが、pdfはアップロードできません)。

私は最も効率的な方法を使用しなかったかもしれませんが、最終的に私が行ったのは、アップロードまたはダウンロードのために、ファイルブラウザーとして機能する小さな組み込み「webapp」をコーディングすることでした。

私の「秘密のプロジェクト」をリリースしない最も近い例は https://encodable.com/filechucker/ になると思います

おそらく、クラウドサービスが時々使用するような簡単な統合ファイルブラウザー(つまり、dropbox)を記述し、カスタムボックスなどで入力を検出するいくつかの関数を使用できます。

ほんの少しの考え。

0
Edward Nunn

jquery.fileDownloadはこれを行うことを可能にします:

$(document).on("click", "a.fileDownloadPromise", function () {
    $.fileDownload($(this).prop('href'))
        .done(function () { alert('File download a success!'); })
        .fail(function () { alert('File download failed!'); });

    return false; 
});

Githubをご覧ください。

https://github.com/johnculviner/jquery.fileDownload

0
Rafael Z.