Skip to content

Commit

Permalink
Fix recording sync (#291)
Browse files Browse the repository at this point in the history
* delete audio/video/recording in remote

* sync recordings on profile page

* handle recording sync failed
  • Loading branch information
an-lee authored Feb 9, 2024
1 parent 27c342c commit 338ef82
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 25 deletions.
14 changes: 13 additions & 1 deletion enjoy/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Client {
});
this.api.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${accessToken}`;
config.headers['Accept-Language'] = locale;
config.headers["Accept-Language"] = locale;

this.logger.debug(
config.method.toUpperCase(),
Expand Down Expand Up @@ -140,10 +140,18 @@ export class Client {
return this.api.post("/api/mine/audios", decamelizeKeys(audio));
}

deleteAudio(id: string) {
return this.api.delete(`/api/mine/audios/${id}`);
}

syncVideo(video: Partial<VideoType>) {
return this.api.post("/api/mine/videos", decamelizeKeys(video));
}

deleteVideo(id: string) {
return this.api.delete(`/api/mine/videos/${id}`);
}

syncTranscription(transcription: Partial<TranscriptionType>) {
return this.api.post("/api/transcriptions", decamelizeKeys(transcription));
}
Expand All @@ -154,6 +162,10 @@ export class Client {
return this.api.post("/api/mine/recordings", decamelizeKeys(recording));
}

deleteRecording(id: string) {
return this.api.delete(`/api/mine/recordings/${id}`);
}

generateSpeechToken(): Promise<{ token: string; region: string }> {
return this.api.post("/api/speech/tokens");
}
Expand Down
5 changes: 4 additions & 1 deletion enjoy/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,5 +444,8 @@
"generatingIpaFailed": "Generating IPA failed",
"translating": "Translating",
"translatedSuccessfully": "Translated successfully",
"translationFailed": "Translation failed"
"translationFailed": "Translation failed",
"allRecordingsSynced": "All recordings synced",
"syncingRecordings": "Syncing {{count}} recordings",
"failedToSyncRecordings": "Syncing recordings failed"
}
5 changes: 4 additions & 1 deletion enjoy/src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,5 +443,8 @@
"generatingIpaFailed": "音标生成失败",
"translating": "正在翻译",
"translatedSuccessfully": "翻译成功",
"translationFailed": "翻译失败"
"translationFailed": "翻译失败",
"allRecordingsSynced": "所有录音已同步",
"syncingRecordings": "{{count}} 条录音正在同步",
"failedToSyncRecordings": "同步录音失败"
}
36 changes: 35 additions & 1 deletion enjoy/src/main/db/handlers/recordings-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
} from "sequelize";
import dayjs from "dayjs";
import { t } from "i18next";
import log from "electron-log/main";

const logger = log.scope("db/handlers/recordings-handler");

class RecordingsHandler {
private async findAll(
Expand Down Expand Up @@ -64,6 +67,36 @@ class RecordingsHandler {
});
}

private async syncAll(event: IpcMainEvent) {
const recordings = await Recording.findAll({
where: { syncedAt: null },
});
if (recordings.length == 0) return;

event.sender.send("on-notification", {
type: "warning",
message: t("syncingRecordings", { count: recordings.length }),
});

try {
recordings.forEach(async (recording) => {
await recording.sync();
});

event.sender.send("on-notification", {
type: "info",
message: t("allRecordingsSynced"),
});
} catch (err) {
logger.error("failed to sync recordings", err.message);

event.sender.send("on-notification", {
type: "error",
message: t("failedToSyncRecordings"),
});
}
}

private async create(
event: IpcMainEvent,
options: Attributes<Recording> & {
Expand Down Expand Up @@ -293,7 +326,7 @@ class RecordingsHandler {
private async groupBySegment(
event: IpcMainEvent,
targetId: string,
targetType: string,
targetType: string
) {
return Recording.findAll({
where: {
Expand Down Expand Up @@ -328,6 +361,7 @@ class RecordingsHandler {
register() {
ipcMain.handle("recordings-find-all", this.findAll);
ipcMain.handle("recordings-find-one", this.findOne);
ipcMain.handle("recordings-sync-all", this.syncAll);
ipcMain.handle("recordings-create", this.create);
ipcMain.handle("recordings-destroy", this.destroy);
ipcMain.handle("recordings-upload", this.upload);
Expand Down
14 changes: 12 additions & 2 deletions enjoy/src/main/db/models/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "sequelize-typescript";
import { Recording, Speech, Transcription, Video } from "@main/db/models";
import settings from "@main/settings";
import { AudioFormats, VideoFormats , WEB_API_URL } from "@/constants";
import { AudioFormats, VideoFormats, WEB_API_URL } from "@/constants";
import { hashFile } from "@/utils";
import path from "path";
import fs from "fs-extra";
Expand Down Expand Up @@ -170,7 +170,7 @@ export class Audio extends Model<Audio> {
const webApi = new Client({
baseUrl: process.env.WEB_API_URL || WEB_API_URL,
accessToken: settings.getSync("user.accessToken") as string,
logger: log.scope("api/client"),
logger: log.scope("audio/sync"),
});

return webApi.syncAudio(this.toJSON()).then(() => {
Expand Down Expand Up @@ -231,6 +231,16 @@ export class Audio extends Model<Audio> {
targetType: "Audio",
},
});

const webApi = new Client({
baseUrl: process.env.WEB_API_URL || WEB_API_URL,
accessToken: settings.getSync("user.accessToken") as string,
logger: log.scope("audio/cleanupFile"),
});

webApi.deleteAudio(audio.id).catch((err) => {
logger.error("deleteAudio failed:", err.message);
});
}

static async buildFromLocalFile(
Expand Down
12 changes: 10 additions & 2 deletions enjoy/src/main/db/models/recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export class Recording extends Model<Recording> {
const webApi = new Client({
baseUrl: process.env.WEB_API_URL || WEB_API_URL,
accessToken: settings.getSync("user.accessToken") as string,
logger: log.scope("api/client"),
logger: log.scope("recording/sync"),
});

return webApi.syncRecording(this.toJSON()).then(() => {
Expand All @@ -160,7 +160,7 @@ export class Recording extends Model<Recording> {
const webApi = new Client({
baseUrl: process.env.WEB_API_URL || WEB_API_URL,
accessToken: settings.getSync("user.accessToken") as string,
logger: log.scope("api/client"),
logger: log.scope("recording/assess"),
});

const { token, region } = await webApi.generateSpeechToken();
Expand Down Expand Up @@ -274,6 +274,14 @@ export class Recording extends Model<Recording> {
@AfterDestroy
static cleanupFile(recording: Recording) {
fs.remove(recording.filePath);
const webApi = new Client({
baseUrl: process.env.WEB_API_URL || WEB_API_URL,
accessToken: settings.getSync("user.accessToken") as string,
logger: log.scope("recording/cleanupFile"),
});
webApi.deleteRecording(recording.id).catch((err) => {
logger.error("deleteRecording failed:", err.message);
});
}

static async createFromBlob(
Expand Down
14 changes: 12 additions & 2 deletions enjoy/src/main/db/models/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "sequelize-typescript";
import { Audio, Recording, Speech, Transcription } from "@main/db/models";
import settings from "@main/settings";
import { AudioFormats, VideoFormats , WEB_API_URL } from "@/constants";
import { AudioFormats, VideoFormats, WEB_API_URL } from "@/constants";
import { hashFile } from "@/utils";
import path from "path";
import fs from "fs-extra";
Expand Down Expand Up @@ -192,7 +192,7 @@ export class Video extends Model<Video> {
const webApi = new Client({
baseUrl: process.env.WEB_API_URL || WEB_API_URL,
accessToken: settings.getSync("user.accessToken") as string,
logger: log.scope("api/client"),
logger: log.scope("video/sync"),
});

return webApi.syncVideo(this.toJSON()).then(() => {
Expand Down Expand Up @@ -254,6 +254,16 @@ export class Video extends Model<Video> {
targetType: "Video",
},
});

const webApi = new Client({
baseUrl: process.env.WEB_API_URL || WEB_API_URL,
accessToken: settings.getSync("user.accessToken") as string,
logger: log.scope("video/cleanupFile"),
});

webApi.deleteVideo(video.id).catch((err) => {
logger.error("deleteAudio failed:", err.message);
});
}

static async buildFromLocalFile(
Expand Down
3 changes: 3 additions & 0 deletions enjoy/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
findOne: (params: object) => {
return ipcRenderer.invoke("recordings-find-one", params);
},
syncAll: () => {
return ipcRenderer.invoke("recordings-sync-all");
},
create: (params: object) => {
return ipcRenderer.invoke("recordings-create", params);
},
Expand Down
8 changes: 7 additions & 1 deletion enjoy/src/renderer/pages/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useState } from "react";
import { useEffect, useState, useContext } from "react";
import {
RecordingStats,
RecordingCalendar,
RecordingActivities,
} from "@renderer/components";
import { AppSettingsProviderContext } from "@renderer/context";
import { Button } from "@renderer/components/ui";
import { ChevronLeftIcon } from "lucide-react";
import { useNavigate } from "react-router-dom";
Expand All @@ -13,11 +14,16 @@ import { t } from "i18next";
export default () => {
const navigate = useNavigate();

const { EnjoyApp } = useContext(AppSettingsProviderContext);
const [range, setRange] = useState<[string, string]>([
dayjs().subtract(7, "day").format(),
dayjs().format(),
]);

useEffect(() => {
EnjoyApp.recordings.syncAll();
}, []);

return (
<div className="h-full px-4 py-6 lg:px-8">
<div className="max-w-5xl mx-auto">
Expand Down
29 changes: 15 additions & 14 deletions enjoy/src/types/enjoy-app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type EnjoyAppType = {
navigatable?: boolean;
}
) => Promise<void>;
show: (bounds: object) => Promise<void>;
show: (bounds: any) => Promise<void>;
hide: () => Promise<void>;
remove: () => Promise<void>;
scrape: (url: string) => Promise<void>;
Expand Down Expand Up @@ -112,26 +112,27 @@ type EnjoyAppType = {
removeListeners: () => Promise<void>;
};
audios: {
findAll: (params: object) => Promise<AudioType[]>;
findOne: (params: object) => Promise<AudioType>;
create: (uri: string, params?: object) => Promise<AudioType>;
update: (id: string, params: object) => Promise<AudioType | undefined>;
findAll: (params: any) => Promise<AudioType[]>;
findOne: (params: any) => Promise<AudioType>;
create: (uri: string, params?: any) => Promise<AudioType>;
update: (id: string, params: any) => Promise<AudioType | undefined>;
destroy: (id: string) => Promise<undefined>;
upload: (id: string) => Promise<void>;
};
videos: {
findAll: (params: object) => Promise<VideoType[]>;
findOne: (params: object) => Promise<VideoType>;
findAll: (params: any) => Promise<VideoType[]>;
findOne: (params: any) => Promise<VideoType>;
create: (uri: string, params?: any) => Promise<VideoType>;
update: (id: string, params: any) => Promise<VideoType | undefined>;
destroy: (id: string) => Promise<undefined>;
upload: (id: string) => Promise<void>;
};
recordings: {
findAll: (where: object) => Promise<RecordingType[]>;
findOne: (where: object) => Promise<RecordingType>;
create: (params: object) => Promise<RecordingType>;
update: (id: string, params: object) => Promise<RecordingType | undefined>;
findAll: (where: any) => Promise<RecordingType[]>;
findOne: (where: any) => Promise<RecordingType>;
syncAll: () => Promise<void>;
create: (params: any) => Promise<RecordingType>;
update: (id: string, params: any) => Promise<RecordingType | undefined>;
destroy: (id: string) => Promise<void>;
upload: (id: string) => Promise<void>;
assess: (id: string) => Promise<void>;
Expand Down Expand Up @@ -165,7 +166,7 @@ type EnjoyAppType = {
findAll: (params: any) => Promise<ConversationType[]>;
findOne: (params: any) => Promise<ConversationType>;
create: (params: any) => Promise<ConversationType>;
update: (id: string, params: object) => Promise<ConversationType>;
update: (id: string, params: any) => Promise<ConversationType>;
destroy: (id: string) => Promise<void>;
ask: (
id: string,
Expand All @@ -181,8 +182,8 @@ type EnjoyAppType = {
) => Promise<MessageType[]>;
};
messages: {
findAll: (params: object) => Promise<MessageType[]>;
findOne: (params: object) => Promise<MessageType>;
findAll: (params: any) => Promise<MessageType[]>;
findOne: (params: any) => Promise<MessageType>;
createInBatch: (messages: Partial<MessageType>[]) => Promise<void>;
destroy: (id: string) => Promise<void>;
createSpeech: (id: string, configuration?: any) => Promise<SpeechType>;
Expand Down

0 comments on commit 338ef82

Please sign in to comment.