Skip to content

Commit

Permalink
Merge pull request #36 from ryoha000/feat/take.screenshot
Browse files Browse the repository at this point in the history
feat: 🎸 chunk
  • Loading branch information
ryoha000 authored Dec 28, 2023
2 parents 7d78b13 + 3313f4f commit ae561d9
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 56 deletions.
46 changes: 46 additions & 0 deletions src/lib/chunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { readBinaryFile } from "@tauri-apps/api/fs";

export const useChunk = () => {
let currentChunkId = 0;
// chunk id は頭につける都合上8bitで表現できるようにする
const chunkIdMask = 0xff;
const CHUNK_SIZE = 16 * 1024; // 16KB
const CHUNK_HEADER_SIZE = 2; // [chunkId: 1byte][index: 1byte]
const CHUNK_DATA_SIZE = CHUNK_SIZE - CHUNK_HEADER_SIZE;
const createNewChunkId = () => {
return (currentChunkId + 1) & chunkIdMask;
};

const createChunks = async (filePath: string) => {
// ファイルをバイナリとして読み込む
const data = await readBinaryFile(filePath);
const lowerCasePath = filePath.toLowerCase();
// MIME タイプを推定 (ここでは ".png" の場合 "image/png" としていますが、他の形式もサポートする場合は調整が必要)
const mimeType = (function () {
if (lowerCasePath.endsWith(".png")) return "image/png";
if (lowerCasePath.endsWith(".jpg") || lowerCasePath.endsWith(".jpeg"))
return "image/jpeg";
if (lowerCasePath.endsWith(".gif")) return "image/gif";
if (lowerCasePath.endsWith(".webp")) return "image/webp";
throw new Error("Unsupported file type");
})();
const chunkId = createNewChunkId();

const totalChunkLength = Math.ceil(data.byteLength / CHUNK_DATA_SIZE);
const chunkArray: Uint8Array[] = [];
for (let i = 0; i < totalChunkLength; i++) {
chunkArray[i] = new Uint8Array([
chunkId,
i,
...data.slice(
i * CHUNK_DATA_SIZE,
Math.min((i + 1) * CHUNK_DATA_SIZE, data.byteLength)
),
]);
}

return [{ chunkId, mimeType, totalChunkLength }, chunkArray] as const;
};

return { createChunks };
};
100 changes: 44 additions & 56 deletions src/store/skyway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import { readBinaryFile } from "@tauri-apps/api/fs";
import { commandSaveScreenshotByPid } from "@/lib/command";
import { getStartProcessMap } from "@/store/startProcessMap";
import { showErrorToast } from "@/lib/toast";
import { useChunk } from "@/lib/chunk";

type PingMessage = { type: "ping" };
type MemoMessage = {
type: "memo";
text: string;
gameId: number;
base64Images: { path: string; base64: string }[];
};
type InitMessage = { type: "init"; gameId: number };
type InitResponseMessage = {
Expand All @@ -34,8 +34,19 @@ type TakeScreenshotMessage = {
gameId: number;
cursorLine: number;
};
type ImageMetadataMessage = {
type: "image_metadata";
path: string;
key: number;
totalChunkLength: number;
mimeType: string;
};

type LocalMessage = PingMessage | MemoMessage | InitResponseMessage;
type LocalMessage =
| PingMessage
| MemoMessage
| InitResponseMessage
| ImageMetadataMessage;
type RemoteMessage =
| PingMessage
| MemoMessage
Expand All @@ -44,34 +55,30 @@ type RemoteMessage =

const createSkyWay = () => {
const roomId = uuidV4();
const [base64ImagesStore, getBase64Images] = createWritable<
{
path: string;
dataUrl: string;
}[]
>([]);
const sentImagePathSet = new Set<string>();
const { createChunks } = useChunk();

const getBase64Image = async (filePath: string) => {
// ファイルをバイナリとして読み込む
const data = await readBinaryFile(filePath);
const lowerCasePath = filePath.toLowerCase();
// MIME タイプを推定 (ここでは ".png" の場合 "image/png" としていますが、他の形式もサポートする場合は調整が必要)
const mimeType = (function () {
if (lowerCasePath.endsWith(".png")) return "image/png";
if (lowerCasePath.endsWith(".jpg") || lowerCasePath.endsWith(".jpeg"))
return "image/jpeg";
if (lowerCasePath.endsWith(".gif")) return "image/gif";
if (lowerCasePath.endsWith(".webp")) return "image/webp";
throw new Error("Unsupported file type");
})();
// DataURL を生成
let binary = "";
for (let i = 0; i < data.byteLength; i++) {
binary += String.fromCharCode(data[i]);
}
const base64 = btoa(binary);
return { dataUrl: `data:${mimeType};base64,${base64}`, path: filePath };
const sendImagesAsChunks = async (imagePaths: string[]) => {
await Promise.all(
imagePaths.map((path) => {
return new Promise<void>(async (resolve) => {
const [{ chunkId, mimeType, totalChunkLength }, chunks] =
await createChunks(path);
const message: ImageMetadataMessage = {
type: "image_metadata",
path,
key: chunkId,
totalChunkLength,
mimeType,
};
sendMessage(message);
chunks.forEach(sendBinaryMessage);
resolve();
});
})
);
};

const getMemoImagePaths = (text: string) => {
const regex = /!\[.*?\]\((.*?)\)/g;
const paths: string[] = [];
Expand Down Expand Up @@ -107,14 +114,8 @@ const createSkyWay = () => {

const createInitResponseMessage = async (workId: number) => {
const { value, imagePaths } = getMemo(workId);
const images = await Promise.all(imagePaths.map(getBase64Image));
base64ImagesStore.update((current) => [
...current,
...images.map((v) => ({
path: v.path,
dataUrl: v.dataUrl,
})),
]);
imagePaths.forEach((path) => sentImagePathSet.add(path));
await sendImagesAsChunks(imagePaths);

const message: InitResponseMessage = {
type: "init_response",
Expand All @@ -123,10 +124,6 @@ const createSkyWay = () => {
type: "memo",
text: value,
gameId: workId,
base64Images: images.map((v) => ({
path: v.path,
base64: v.dataUrl,
})),
},
};
return message;
Expand Down Expand Up @@ -254,33 +251,24 @@ const createSkyWay = () => {

dataStream.write(JSON.stringify(message));
};
const sendBinaryMessage = (message: Uint8Array) => {
if (!dataStream) return;

dataStream.write(message);
};

const syncMemo = async (workId: number, text: string) => {
if (!dataStream) return;
const imagePaths = getMemoImagePaths(text);
const notSharedImages = imagePaths.filter(
(path) =>
getBase64Images().findIndex((image) => image.path === path) === -1
);
const images = await Promise.all(
notSharedImages.map((path) => getBase64Image(path))
(path) => !sentImagePathSet.has(path)
);
base64ImagesStore.update((current) => [
...current,
...images.map((v) => ({
path: v.path,
dataUrl: v.dataUrl,
})),
]);
await sendImagesAsChunks(notSharedImages);

const message: MemoMessage = {
type: "memo",
text,
gameId: workId,
base64Images: images.map((v) => ({
path: v.path,
base64: v.dataUrl,
})),
};
sendMessage(message);
};
Expand Down

0 comments on commit ae561d9

Please sign in to comment.