シンプルな機能の裏に掛かる労働時間

このウェブサイトを2021年4月から開始し、ユーザーはパスワード保護されいるPDFファイルをアップロードをアップロードしたい事は分かっていた。これらのPDFのサポートを行う事は面倒だった為、手を付けなかった。ただ、最近、ユーザーからパスワード保護が掛かったPDFを対応してくれないかと依頼があった為、やる事にした。

これはシンプルな機能で結構うまくいきます。昨日はほとんど丸一日作業をしていて、今朝午前2:32まで仕事をしていました。この数日収集した少量のデータによると、約5%のユーザーがパスワード付のPDFをアップロードしている事が分かった。今までは画面にエラーメッセージが出る前にアップロードする事自体却下していた。これらのほとんどのユーザーは、ウェブサイト自体上手くいかないと想定し、どこかへ消えてしまった。

この機能を実現するに辺り掛かった時間には驚き、不満を感じたので、あなたが私に感謝出来る様、私があなたの為に行った内容を共有します。1

土曜日 午後11:46 – ファイルマッピングテーブルにフィールドを追加する。

ファイルマッピングテーブルはユーザーがアップロードした各PDFの情報を保存する。2つのフィールドパスワードパスワード必須を追加した。 このアイディアはパスワードやその他書類のメタデータをデータベースへ保存する事だ。

日曜日 午前11:10 – APIが保護されたPDFを受け取った場合

APIを変えて、ファイルマッピング を作成し、パスワード必須で記録し、PDF種類は不明とした。 以前まではPDFはTEXT_BASEDIMAGE_BASEDで対応する事ができた。パスワード無しではファイルを読むことは出来ないので、TEXT_BASED か IMAGE_BASEDか分析し決定する事も出来ない。

これをする理由はテキストかイメージのPDFによってコードが別になるからだ。

日曜日 午前1:14 - SQLを書き、ファイルマッピングレコードをアップロード

UPDATE file_mapping SET password = ? WHERE uuid = ?;

日曜日 午後 2:55 – SQLを変更し、APIの作成をし書類のパスワードを設定

UPDATE file_mapping SET password = ? pdf_type = ? page_count = ? WHERE uuid = ?;

APIのコーディングを行い、ユーザーがパスワード設定をする事に成功した後、フィールドマッピングのフィールドを変えたいと思い始めた。書類を読んだ後、PDF種類を分類し、何ページあるかカウントするが可能となる。

val fileMappings = repository.getFileMappings(body.passwords.map { it.uuid })
val updates = mutableMapOf<String, UpdateFileMapping>()

val results = fileMappings.map { mapping ->
    mapping.validateOwnership(userId, ipAddress)
    val password = body.passwords.first { it.uuid == mapping.uuid}.password
    val file = File(mapping.filename)
    val result = uploadAction.analysePdf(file, password, userId, ipAddress, mapping.uuid, mapping.originalFilename)

    if (result.state != UploadResponse.State.REQUIRES_PASSWORD) {
        updates[mapping.uuid] = UpdateFileMapping(password, result.pdfType.toString(), result.numberOfPages)
    }

    result
}

// Set the passwords
repository.updateFileMappings(updates)

call.respondText(contentType = ContentType.Application.Json) {
    stringify(results)
}

APIは書類識別子やパスワードを受け入れています。その後、提供されたパスワードで書類を開く事を試みます。データベースに正しいパスワードを書くのです。そして提供されたパスワードでどの書類を開く事に成功できたかの返答をします。

日曜日 午後3:21 - 書類開示コードを変更し、可能な際にパスワードを使用できる様にした。

val document = if (password == null) Loader.loadPDF(statement) else Loader.loadPDF(statement, password)

パスワードを持っているのであれば、使ってください。これは実際にはとてもシンプルです。Loader.loadPDFにてパスワードの無効値を喜んで受け入れてくれます。

日曜日 午後11:57 - 全てのLoader.loadPDFにパスワードを提供してもらうのは結

構簡単ですが、保護されたPDFの全ての変換路を確認する為の様々なテストを作成しました。時間に大きなギャップがあるのは、親父の家に行って、昼寝をし、夕飯を食べてから香港フィルムアワードを見ていたからです。

月曜日 午前12:30 – DEV内のフォルダー消去バグの修正

定期的にPDFなどのユーザーデータのクリーンアップの仕事を行う事があり、画像や光学式文字認識の結果などを提供している。

for (directory in directories) {
    for (file in directory.walk()) {
        // delete file
    }
}

ディレクトリが空の場合、上記のコードはディレクトリから消去される。プロダクションサーバーにはこれが発生しない理由は人々は常にアプリを使用している為、ディレクトリが空になる事が無いからだ。ただし、開発サーバーでは発生します! この問題に関しては下記のコードでバグを修正した。

fun start(): AccountingProApp {
    tempFileDirectories.forEach {
        if (!it.exists()) {
            it.mkdirs()
        }
    }
    startServerWithRetries()
    cleanup.scheduleExecution()
    imagePdfWorker.scheduleExecution()
    return this
}

アプリ立ち上げ時にディレクトリが存在しない場合、コードが作成をします。ただ、この投稿を書いてる際に、この修正法はあまり良くない事に気づきました。空のフォルダーは削除する事はできますが、取り戻す為にはサーバーをバウンスする必要があります。開発サーバーは毎回コードを再移動する度に頻繁にバウンスします。なので、余分な手間を省くためにフォルダーを削除しない解決法を思いつきました。

月曜 午前2:24 - UIが書類のパスワード設定を認める

<Input onChange={onChange} value={value} placeholder="Password"/>
<Button
  onClick={onClick}
  css={{ marginLeft: '0.25em' }}
  size="compact"
  emphasis="primary">
  Submit
</Button>

保護されたPDFをアップロードする際にインプットエレメントを提供します。提出ボタンを押す際にAPIを押します。ブラウザにAPIの回答を一致させます。私はそんなにフロントエンドコードを書くのは上手ではないので、とても面倒でした。

見た目も良くないですし、インプットボックスも大きすぎます。CSSを書くのは好きではないですし、機能的には大丈夫そうなので、ここでストップしました。

月曜日 午前2:32 - APIのロジック修正

PDFベースのテキストは描写や文字認識されている事に気づいた。ただ正しくはない。なので、バグ修正を行った。コードをプロダクションへ入れ込んだ後、就寝した。

月曜日 午前11:13 - UI最後の仕上げ

私のオフィスへ行き、Grafanaダッシュボードを確認すると、誰かがパスワード機能に成功したとの事だった。これは嬉しい!ローディングインディケーターを追加し、インプットエレメントを小さくしました。

<Input
  size="compact"
  onChange={onChange}
  value={value}
  placeholder="Password"
  disabled={disabled}
/>

<Button
  onClick={onClick}
  css={{ marginLeft: '0.25em' }}
  size="compact"
  emphasis="primary"
  disabled={disabled}
  decoratorLeft={decoratorLeft}
>
  Submit
</Button>

Monday 11:38 AM - Fixed another API bug

// Before
val state = if (it.uuid in inProgressUuids) UploadResponse.State.PROCESSING else UploadResponse.State.READY

// After
val state = when {
    it.uuid in inProgressUuids -> UploadResponse.State.PROCESSING
    it.pdfType == PDFType.UNKNOWN -> UploadResponse.State.REQUIRES_PASSWORD
    else -> UploadResponse.State.READY
}

コードは2つの選択肢、READYかPROCESSINGしかない。この新しい機能にはREQUIRES_PASSWORDが加わった。

結論

もう一度こちらのデモをご確認下さい。こんなにシンプルな機能に数日掛かり、香港映フィルムアワードも観てやっと完了したとは信じられませんね。

この仕事をやって良かったか?それは時間が経てば分かるでしょう。この投稿を2032年に見ている方がいましたらぜひ連絡くだされば、この機能でどの位お金を稼ぐ事が出来たか分析します。“保護ファイルをアップロードしたユーザーからの収益を計算します”。2033年になったら、この分析を行います。


  1. Your money ↩︎

Join The Mailing List