web 磁盘文件缓存增删改查操作

14 阅读2分钟
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>FileDirectoryStoreManager 测试</title>
    <style>
      body {
        font-family: sans-serif;
        margin: 20px;
      }
      button {
        margin: 5px;
        padding: 5px 10px;
      }
      pre {
        background: #f0f0f0;
        padding: 10px;
      }
    </style>
  </head>
  <body>
    <h1>FileDirectoryStoreManager 测试</h1>

    <button id="openDir">打开目录</button>
    <button id="addFile">新增文件</button>
    <button id="readFile">读取文件</button>
    <button id="updateFile">更新文件</button>
    <button id="checkExist">检查文件是否存在</button>
    <button id="listKeys">列出所有文件</button>
    <button id="removeFile">删除文件</button>

    <pre id="output"></pre>

    <script>
      // FileDirectoryStoreManager.js
      // Web FileSystem Access API + key 存储
      // 多文件,直接写入文件,支持增删改查
      // FileDirectoryStoreManager.js
      // Web FileSystem Access API + key 存储 + 自动记住授权 (纯 IndexedDB)
      class FileDirectoryStoreManager {
        constructor() {
          this.dirHandle = null;
          this.dbName = "FileDirectoryStoreDB";
          this.storeName = "handles";
          this.keyName = "projectDir";
        }

        // 初始化 IndexedDB
        _getDB() {
          return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.dbName, 1);
            request.onupgradeneeded = (event) => {
              const db = event.target.result;
              if (!db.objectStoreNames.contains(this.storeName)) {
                db.createObjectStore(this.storeName);
              }
            };
            request.onsuccess = (event) => resolve(event.target.result);
            request.onerror = (event) => reject(event.target.error);
          });
        }

        // 保存目录句柄
        async _saveHandle() {
          if (!this.dirHandle) return;
          const db = await this._getDB();
          const tx = db.transaction(this.storeName, "readwrite");
          tx.objectStore(this.storeName).put(this.dirHandle, this.keyName);
          return new Promise((resolve, reject) => {
            tx.oncomplete = () => resolve(true);
            tx.onerror = (e) => reject(e.target.error);
          });
        }

        // 加载目录句柄
        async _loadHandle() {
          const db = await this._getDB();
          const tx = db.transaction(this.storeName, "readonly");
          const handle = tx.objectStore(this.storeName).get(this.keyName);
          return new Promise((resolve) => {
            handle.onsuccess = async (e) => {
              const dirHandle = e.target.result;
              if (!dirHandle) return resolve(null);

              // 检查权限
              let perm = await dirHandle.queryPermission({ mode: "readwrite" });
              if (perm === "granted") return resolve(dirHandle);

              perm = await dirHandle.requestPermission({ mode: "readwrite" });
              if (perm === "granted") return resolve(dirHandle);

              resolve(null);
            };
            handle.onerror = () => resolve(null);
          });
        }

        // 检查目录是否打开
        _assertDir() {
          if (!this.dirHandle) throw new Error("Project directory not opened");
        }

        // 打开/选择工程目录(自动记住授权)
        async openDir() {
          // 尝试加载已保存的目录句柄
          this.dirHandle = await this._loadHandle();
          if (this.dirHandle) return this.dirHandle;

          // 没有可用的句柄,则弹出选择框
          this.dirHandle = await window.showDirectoryPicker();
          await this._saveHandle();
          return this.dirHandle;
        }

        // 新增 / 创建文件
        async add(key, fileData) {
          this._assertDir();
          const fileHandle = await this.dirHandle.getFileHandle(key, {
            create: true,
          });
          const writable = await fileHandle.createWritable();
          await writable.write(fileData);
          await writable.close();
          return true;
        }

        // 读取文件
        async read(key) {
          this._assertDir();
          const fileHandle = await this.dirHandle.getFileHandle(key);
          return await fileHandle.getFile();
        }

        // 更新文件(覆盖写)
        async update(key, fileData) {
          return this.add(key, fileData);
        }

        // 删除文件
        async remove(key) {
          this._assertDir();
          await this.dirHandle.removeEntry(key);
          return true;
        }

        // 判断文件是否存在
        async exists(key) {
          this._assertDir();
          try {
            await this.dirHandle.getFileHandle(key);
            return true;
          } catch {
            return false;
          }
        }

        // 列出所有 key
        async listKeys() {
          this._assertDir();
          const keys = [];
          for await (const [name, handle] of this.dirHandle.entries()) {
            if (handle.kind === "file") keys.push(name);
          }
          return keys;
        }
      }


      // import FileDirectoryStoreManager from './FileDirectoryStoreManager.js';

      // const store = new FileDirectoryStoreManager();

      // // 1️⃣ 打开工程目录
      // await store.openDir();

      // // 2️⃣ 新增 / 写入文件
      // const blob = new Blob(['Hello world'], { type: 'text/plain' });
      // await store.add('myfile1', blob);

      // // 3️⃣ 读取文件
      // const file = await store.read('myfile1');
      // console.log(await file.text()); // "Hello world"

      // // 4️⃣ 更新文件
      // const newBlob = new Blob(['Updated content'], { type: 'text/plain' });
      // await store.update('myfile1', newBlob);

      // // 5️⃣ 判断是否存在
      // console.log(await store.exists('myfile1')); // true

      // // 6️⃣ 列出所有 key
      // console.log(await store.listKeys());

      // // 7️⃣ 删除文件
      // await store.remove('myfile1');
    </script>

    <script>
      const store = new FileDirectoryStoreManager();
      const output = document.getElementById("output");

      function log(msg) {
        console.log(msg);
        output.textContent += msg + "\n";
      }

      document.getElementById("openDir").onclick = async () => {
        try {
          await store.openDir();
          log("✅ 已打开目录");
        } catch (err) {
          log("❌ 打开目录失败: " + err);
        }
      };

      document.getElementById("addFile").onclick = async () => {
        try {
          const blob = new Blob(["Hello world"], { type: "text/plain" });
          await store.add("myfile1.txt", blob);
          log("✅ 已新增文件 myfile1.txt");
        } catch (err) {
          log("❌ 新增文件失败: " + err);
        }
      };

      document.getElementById("readFile").onclick = async () => {
        try {
          const file = await store.read("myfile1.txt");
          const text = await file.text();
          log(`📖 文件内容: ${text}`);
        } catch (err) {
          log("❌ 读取文件失败: " + err);
        }
      };

      document.getElementById("updateFile").onclick = async () => {
        try {
          const blob = new Blob(["Updated content"], { type: "text/plain" });
          await store.update("myfile1.txt", blob);
          log("✏️ 已更新文件 myfile1.txt");
        } catch (err) {
          log("❌ 更新文件失败: " + err);
        }
      };

      document.getElementById("checkExist").onclick = async () => {
        try {
          const exists = await store.exists("myfile1.txt");
          log(`🔍 文件是否存在: ${exists}`);
        } catch (err) {
          log("❌ 检查存在失败: " + err);
        }
      };

      document.getElementById("listKeys").onclick = async () => {
        try {
          const keys = await store.listKeys();
          log("🗂️ 所有文件: " + keys.join(", "));
        } catch (err) {
          log("❌ 列出文件失败: " + err);
        }
      };

      document.getElementById("removeFile").onclick = async () => {
        try {
          await store.remove("myfile1.txt");
          log("🗑️ 已删除文件 myfile1.txt");
        } catch (err) {
          log("❌ 删除文件失败: " + err);
        }
      };
    </script>
  </body>
</html>