narou icon indicating copy to clipboard operation
narou copied to clipboard

Kindle 2024 でファイル転送できない

Open nnonaka opened this issue 1 year ago • 6 comments

Kindle2024からPC接続時のファイル転送はMTPプロトコルが使われるようになり、KindleをPCにUSBケーブルで接続してもドライブレターが割り当てられなくなったため、narou.rbからKindleが接続されたことを検知できず、Sendコマンドが使えなくなりました。

nnonaka avatar Oct 18 '24 05:10 nnonaka

ドライブレターを割り当てるツールとスクリプトについてgistにあげてます。参考までに。 https://gist.github.com/rogenobl/f99d989afd48513cbda3b92de034dd53

MTPプロトコルを扱うrubyのライブラリを見つける事ができませんでした。 いちおう、OLE経由でエクスプローラーを操作すればファイル操作できそうですが、それなりにコードを書く必要があると思います。 だれかライブラリ作ってくれると良いんですが。

rogenobl avatar Jan 05 '25 08:01 rogenobl

情報ありがとうございます。今の所narou.rbの設定で"convert.copy-to"を指定して、格納されたファイルを手動でエクスプローラー上のKindleのdocumentsフォルダーにコピーすることで暫定運用していますが、コピー時に大量のファイルを一度にコピーするとエラーになるので何回かに分けてコピーするなどしており、MTPでのマウントは今一つ信頼性に欠けるという認識です。 せっかくご教示いただいたのですが、dokanの信頼性が気になるので私個人としては今の暫定運用を続けようかと思っております。 Amasonが方針を元に戻してもらうのが一番なのですが、まずそんなことはありそうにないので、悩ましいですね。

nnonaka avatar Jan 05 '25 12:01 nnonaka

[!NOTE] 下記の Shell.Application を使う方法は駄目そう。

Kindle 2024 がないので動作確認できませんが、エクスプローラのPCの下に表示されるデバイス名が Kindle のみの場合は、以下で Kindle デバイスの検出とファイルのコピーはできそうです。 (MTP接続のAndroid端末でデバイス検出とファイルのコピーを確認し、Kindle 用に名前を変えたもの)

require 'win32ole'

SHELL_APPLICATION = WIN32OLE.new("Shell.Application")

def get_device_root_dir(volume_name)
  volume = SHELL_APPLICATION.NameSpace(0x11).Items.each.select do |item|
    name = item.Name
    name = $` if name =~ / \([A-Z]:\)$/ # ドライブレターがあったら削除
    name.casecmp(volume_name) == 0
  end.first
  volume ? volume.Path.gsub('\\', '/') : nil
end

def copy_file(src_file, dest_directory)
  dest = SHELL_APPLICATION.NameSpace(dest_directory.gsub('/', '\\'))
  # マジックナンバー16でファイルが存在するとき上書き
  # 参照: https://learn.microsoft.com/ja-jp/windows/win32/shell/folder-copyhere
  dest.CopyHere(src_file.gsub('/', '\\'), 16)
end

def file_exist?(filename)
  dir, file = File.split(filename)
  begin
    folder = SHELL_APPLICATION.NameSpace(dir.gsub('/', '\\'))
  rescue WIN32OLE::RuntimeError
    return false
  end
  folder && folder.Items.each.any? do |item|
    item.Name == file
  end
end

def file_mtime(filename)
  dir, file = File.split(filename)
  folder = SHELL_APPLICATION.NameSpace(dir.gsub('/', '\\'))
  return nil if folder.nil?
  item = folder.Items.each.find do |item|
    item.Name == file
  end
  return nil if item.nil?
  item.ModifyDate
end

def is_directory?(filename)
  begin
    SHELL_APPLICATION.NameSpace(filename.gsub('/', '\\'))
    true
  rescue WIN32OLE::RuntimeError
    false
  end
end

file_name = 'documentsの下にコピーするローカルディスクのファイル名'

kindle_device = get_device_root_dir('Kindle')
documents_dir = File.join(kindle_device, 'documents')
copy_file(File.absolute_path(file_name), documents_dir)

lib/device/library/windows.rbget_device_root_dir を上のものに置き換えて lib/device.rbFile.directory?, File.exists?, File.mtime? とファイルコピー部分を上の関数に置き換えればなんとかすればいけるのではないかと。

理想的には device.rbFile のクラスメソッドの代わりに Pathname のメソッドを使うようにして、 get_device_root_dir の戻り値は USB デバイスの場合は Pathname のインスタンス、MTPデバイスの場合は device.rb の必要とするメソッドを実装した Pathname もどきにしたほうがきれいだとは思う。

問題点:

  1. CopyHere() でのファイルコピーは呼び出し側はコピーの終了待ちやコピー失敗の検知ができない。代わりにダイアログボックスが画面に表示される。
  2. Android端末の場合、ファイルのコピー後すぐにはエクスプローラに反映されない。他のフォルダをいくつも表示し、しばらく時間が過ぎてから反映されていることがあるが、どのタイミングで反映されるのか不明。Kindle 2024 でも同様かも
  3. 引数のファイル名は絶対パスで指定。ドライブレターが割り当てられているデバイスの場合は File.absolute_path で絶対パスにできるが、MTPの絶対パスに対して使うとパス名が壊される
  4. グローバル変数 SHELL_APPLICATION を定義しているが、 lib/device/library/windows.rb@@FileSystemObject のようにクラス変数にしたほうが良い
  5. エラーチェックは不十分かと思う

普段、Windows を使ってないので、ここまで。

kubo avatar Jan 12 '25 09:01 kubo

kuboさんのスクリプトを試してみました。

実際のデバイス名とフォルダー構成は私の場合   PC\Kindle Paperwhite Signature Edition\Internal Storage\documents となっていて、デバイス名は機種毎に異なるものと思われます。そこで

  • name.casecmp(volume_name) == 0 > name.start_with?(volume_name)
  • documents_dir = File.join(kindle_device, 'documents') > documents_dir = File.join(kindle_device, 'Internal Storage/documents')

に変更し、テストファイルのコピーを試したところ、copy_fileはエラーなしで呼べたのですが、ダイアログは出ずにコピーもされませんでした。

一例なので、何か環境依存があるかどうかまでは調べられていません。

nnonaka avatar Jan 13 '25 14:01 nnonaka

動作確認ありがとうございます。

Shell.Application をWIN32OLEで使う方式はどうやら駄目そうですね……。 あと、この方式はエクスプローラでの操作と基本的に同じなので、エクスプローラで大量ファイルをコピーすると失敗するという現象の回避にはならないでしょう。 また、file_mtime() の動作確認していたとき、USBドライブもMTPもAPIの使い方は同じだろうという甘い考えのもとに、Kindle 10th を繋いてタイムスタンプが正しく取れることは確認した。しかし、MTP接続の Android 上のファイルに使うと 全部1899-12-30 00:00:00 +0900 といった変な値。使えるものではなかった。

kubo avatar Jan 14 '25 13:01 kubo

1899-12-30 00:00:00は、Accessや古いSQL Serverでの日時のデフォルト値で、SQL Server は定義された時間ソースに接続できない場合、この日付をデフォルトとして返す。

だからちゃんとした専用のSQL構文を組まないとダメかも

kita77777 avatar Mar 14 '25 16:03 kita77777