基于Tauri、IPFS简单的网盘,软软AI中加入网盘

672 阅读2分钟

基于Tauri、IPFS简单的网盘,软软AI中加入网盘

Tauri中启动可执行程序,软软AI中集成IPFS中简单介绍了Tauri的项目中集成IPFS。这里简单实现下类似一个IPFS的UI,可以当成P2P网盘类使用。

首先看下实现之后的效果图

界面比较简单

1684370235788.jpg

简单的设计逻辑

  • 文件的存储使用IPFS的能力。
  • 文件的列表、目录等结构存储在sqlite数据库中
  • 关于需要Pin的文件,使用IPFS Cluster的Pin功能实现(后续)

后端

sqlite 存储文件列表

sqlite 数据表设计

CREATE TABLE rrai_files (
    `id` INTEGER PRIMARY KEY,
    `parent_id` INTEGER DEFAULT 0,
    `cid` TEXT DEFAULT '',
    `is_pin` INTEGER DEFAULT 0,
    `file_name` TEXT DEFAULT '',
    `file_hash` TEXT DEFAULT '',
    `file_type` TEXT DEFAULT '',
    `category` TEXT DEFAULT '',
    `avatar` TEXT DEFAULT '',
    `is_dir` INTEGER DEFAULT 0,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TRIGGER rrai_files_updated AFTER UPDATE ON rrai_files
BEGIN
    UPDATE rrai_files SET updated_at = CURRENT_TIMESTAMP WHERE id = new.id;
END;

Tauri插件的编写

mod constants;
mod files;
mod handlers;
mod ipfs;
mod migration;
mod models;

use tauri::{
    plugin::{Builder, TauriPlugin},
    Runtime,
};

/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> {
    //异步非阻塞
    tauri::async_runtime::spawn(async move {
        //启动 ipfs
        let (mut rx, mut child) = tauri::api::process::Command::new_sidecar("ipfs")
            .expect("failed to create `ipfs` binary command")
            .args(["daemon"])
            .spawn()
            .expect("Failed to spawn sidecar");

        // read events such as stdout
        while let Some(event) = rx.recv().await {
            if let tauri::api::process::CommandEvent::Stdout(line) = event {
                tracing::debug!("IPFS:{}", line);
            }
        }
    });
    //
    tauri::async_runtime::block_on(async move {
    //判断是否启动 ipfs
    //判断是否存在配置文件

    //执行数据库脚本
    tracing::debug!("执行数据库脚本");
        migration::init_database().await
    })
    .expect("执行数据库脚本失败!");

    Builder::new("rrai-storage")
        .invoke_handler(tauri::generate_handler![
            handlers::list_files,
            handlers::list_files_by_category,
            handlers::insert_file,
            handlers::update_file,
            handlers::delete_file,
            handlers::create_dir,
            handlers::ipfs_add_content,
            handlers::ipfs_get_content,
        ])
        .build()

}

提供文件的一些操作接口以及IPFS上添加和下载内容的接口。

文件列表的操作主要是sqlite的CRUD,这里不做描述。

IPFS的操作

这里使用IPFS的RUST的客户端库 ipfs-api-backend-hyper 这个库使用的是HTTP的接口。所以在启动IPFS的时候需要启动HTTP服务。这里需要注意的是不能直接使用IPFS的js的库来实现,那个依赖的node环境。

后端的代码在tauri-plugin-rrai-storage这个插件中,详细可参见github

github.com/rrkeji/rrai…

前端

首先封装后端插件

import { invoke } from '@tauri-apps/api/tauri'

export const listFiles = async (parentId: number): Promise<FileEntity[]> => {
    let res = await invoke('plugin:rrai-storage|list_files', { parentId: parentId });
    console.log(res);
    return res as FileEntity[];
}

export const listFilesByCategory = async (parentId: number, category: string, limit: number,): Promise<FileEntity[]> => {
    let res = await invoke('plugin:rrai-storage|list_files_by_category', {
    'parentId': parentId,
    'category': category
    });
    console.log(res);
    return res as FileEntity[];
}

//file_type 0 文件 1 文件夹
export const createFile = async (request: FileEntity): Promise<number> => {
    let res = await invoke('plugin:rrai-storage|insert_file', {
    'file': request,
    });
    console.log(res);
    return 0;
}

export const updateFile = async (id: number, parentId: number, fileName: string, category: string, avatar: string): Promise<number> => {
    let res = await invoke('plugin:rrai-storage|update_file', {
        'id': id,
        'parentId': parentId,
        'fileName': fileName,
        'category': category,
        'avatar': avatar
    });
    console.log(res);
    return 0;
}

export const deleteFile = async (fileId: number): Promise<boolean> => {
    let res = await invoke('plugin:rrai-storage|delete_file', {
    'id': fileId,
    });
    console.log(res);
    return true;
}

export const createDir = async (
    parentId: number,
    fileName: string,
): Promise<boolean> => {

    let res = await invoke('plugin:rrai-storage|create_dir', {
    'parentId': parentId,
    'fileName': fileName
    });
    console.log(res);
    return true;
}

export const addContent = async (data: Uint8Array): Promise<string | null> => {

    let res = await invoke('plugin:rrai-storage|ipfs_add_content', {
    'data': Array.from(data),
    });
    console.log(res);
    return res;
}

export const getContent = async (cid: string): Promise<Uint8Array | null> => {
    let res = await invoke('plugin:rrai-storage|ipfs_get_content', {
    'cid': cid,
    });
    console.log(res);
    return res;
}

页面

页面的代码较为简单,可以参见github

github.com/rrkeji/rrai…