SaveFilePlugin.ets 6.46 KB
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';

import { picker } from '@kit.CoreFileKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import UIAbility from '@ohos.app.ability.UIAbility';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { filePreview } from '@kit.PreviewKit';
import { window } from '@kit.ArkUI';


/** SaveFilePlugin **/
export default class SaveFilePlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;
  private binding: FlutterPluginBinding | null = null;
  private uiContext: Context | null  = null;

  constructor() {
  }

  getUniqueClassName(): string {
    return "SaveFilePlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "save_file");
    this.channel.setMethodCallHandler(this)
    this.binding = binding;



    this.uiContext = binding.getApplicationContext();
      // this.getUIContext().getHostContext() as Context;
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
    }
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method == "getPlatformVersion") {
      result.success("OpenHarmony ^ ^ ")
    } else if (call.method == "saveFile") {
      // result.success("OpenHarmony ^ ^ ")
      const filePath = call.argument("path") as string || ""
      const fileName = call.argument("filename") as string || ""
      // 定义文件路径,这里假设要保存到应用的内部存储某个目录下,可根据实际需求调整。
      console.info(`沙箱路径: ${filePath} `);
      this.smartSaveToPublic(filePath, fileName, result);

    } else {
      result.notImplemented()
    }
  }

  async smartSaveToPublic(
    // context: common.UIAbilityContext,
    sourceUri: string,
    fileName: string,
    result: MethodResult
  ): Promise<boolean> {
    // 策略1: 用户选择保存位置
    try {
      const documentSaveOptions = new picker.DocumentSaveOptions();
      const fileNameStr = fileName + ".xlsx";
      //   documentSaveOptions.newFileNames = [fileNameStr];
      //   documentSaveOptions.fileSuffixChoices = ['文档|.xlsx', '.xlsx'];
      documentSaveOptions.newFileNames = [fileNameStr];

      const documentViewPicker = new picker.DocumentViewPicker();
      const result1 = await documentViewPicker.save(documentSaveOptions);

      if (result1 && result1.length > 0) {
        const destUri = result1[0];
        await this.reliableCopyFile(sourceUri, destUri, result,fileNameStr);

      }
    } catch (err) {
      console.warn(`用户选择保存失败: ${err.message}`);
      return false;
    }
    return false;

    // // 策略2: 直接保存到公共Documents目录
    // try {
    //   const publicPath = "/storage/media/100/local/files/Documents/" + fileName;
    //   const success = await reliableCopyFile(sourceUri, publicPath);
    //
    //   if (success) {
    //     return { success: true, path: publicPath, method: "public-documents" };
    //   }
    // } catch (err) {
    //   console.warn(`公共目录保存失败: ${err.message}`);
    // }
    //
    // return { success: false, error: "所有保存方法都失败", method: "failed" };
  }

  openPreview(
    title:string,
    sourceUri: string,
  ){

    let displayInfo: filePreview.DisplayInfo = {
      x: 100,
      y: 100,
      width: 800,
      height: 800
    };
    let fileInfo: filePreview.PreviewInfo = {
      title: title,
      uri: sourceUri,
      mimeType: ''
    };
    filePreview.openPreview(this.uiContext, fileInfo, displayInfo).then(() => {
      console.info('Succeeded in opening preview');
    }).catch((err: BusinessError) => {
      console.error(`Failed to open preview, err.code = ${err.code}, err.message = ${err.message}`);
    });

  }


  async reliableCopyFile(
    sourceUri: string,
    destUri: string,
    result: MethodResult,
    title:string
  ): Promise<boolean> {
    let sourceFile: fs.File | undefined;
    let destFile: fs.File | undefined;

    try {
      // 获取源文件信息
      const sourceStat = fs.statSync(sourceUri);
      console.info(`源文件大小: ${sourceStat.size} bytes`);

      if (sourceStat.size === 0) {
        throw new Error("源文件大小为0");
      }

      // 打开源文件和目标文件
      sourceFile = fs.openSync(sourceUri, fs.OpenMode.READ_ONLY);
      destFile = fs.openSync(
        destUri,
        fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY
      );

      // 分块复制
      const bufferSize = 64 * 1024; // 64KB缓冲区
      let totalCopied = 0;

      while (totalCopied < sourceStat.size) {
        const remainingBytes = sourceStat.size - totalCopied;
        const bytesToRead = Math.min(bufferSize, remainingBytes);

        // 读取数据
        const buffer = new ArrayBuffer(bytesToRead);
        const bytesRead = fs.readSync(sourceFile.fd, buffer, {
          offset: totalCopied,
        });

        if (bytesRead === 0) {
          break; // 到达文件末尾
        }

        // 写入数据
        const actualBuffer = buffer.slice(0, bytesRead);
        fs.writeSync(destFile.fd, actualBuffer);

        totalCopied += bytesRead;

        // 显示进度
        const progress = ((totalCopied / sourceStat.size) * 100).toFixed(1);
        console.info(
          `复制进度: ${progress}% (${totalCopied}/${sourceStat.size})`
        );
      }

      // 强制同步到磁盘
      fs.fsyncSync(destFile.fd);
      console.info("数据同步到磁盘完成");

      // 验证复制结果
      if (totalCopied === sourceStat.size) {
        console.info("✅ 手动复制成功,文件大小匹配");
        // this.openPreview(title,destUri)
        result.success(destUri)
        return true;
      } else {
        console.error(
          `❌ 复制字节数不匹配: 源=${sourceStat.size}, 复制=${totalCopied}`
        );
        return false;
      }
    } catch (err) {
      console.error(`手动复制失败: ${err.code}, ${err.message}`);
      return false;
    } finally {
      // 清理资源
      if (sourceFile) {
        try {
          fs.closeSync(sourceFile.fd);
        } catch (e) {
        }
      }
      if (destFile) {
        try {
          fs.closeSync(destFile.fd);
        } catch (e) {
        }
      }
    }
  }
}