Skip to content

Commit

Permalink
v1.2.8-beta.2 (#854)
Browse files Browse the repository at this point in the history
* ✔ Performance fixes
- Fixed slide bg cloud preview not showing up
- Optimized media drawer
- Fixed color pattern file in prod
- Auto fix for broken files
- Async media file location
- Auto delete large history file

* ✔ Async file reading
- Updated media type filter
- Fixed textbox jumping when moving to negative values

* 🕐 Scripture verse history
- Confirm closing output window
- Fixes .pro issues
- Change stage label colors
- Fixed stage text style tools not working
- Better stage reset

* ✨ Small tweaks

* 📄 HTTP API query
- Updated Turkish language
- Auto rename output on creation
- Version update
  • Loading branch information
vassbo authored Sep 27, 2024
1 parent fbc4ae8 commit cd37688
Show file tree
Hide file tree
Showing 45 changed files with 615 additions and 242 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "freeshow",
"version": "1.2.8-beta.1",
"version": "1.2.8-beta.2",
"private": true,
"main": "build/electron/index.js",
"description": "Show song lyrics and more for free!",
Expand Down
6 changes: 4 additions & 2 deletions public/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@
"settings": "Settings",
"_title_settings": "Settings",
"_title_display": "Present",
"_title_display_stop": "Stop presentation"
"_title_display_stop": "Stop presentation",
"again_confirm": "Click again to confirm"
},
"empty": {
"general": "Nothing here",
Expand Down Expand Up @@ -977,7 +978,8 @@
"overrun": "Overrun Color",
"source_output": "Source output",
"auto_stretch": "Auto stretch content",
"labels": "Show labels"
"labels": "Show labels",
"label_color": "Label color"
},
"settings": {
"general": "General",
Expand Down
124 changes: 114 additions & 10 deletions public/lang/tr.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/electron/cloud/drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from "path"
import { isProd, toApp } from ".."
import { STORE } from "../../types/Channels"
import { stores } from "../data/store"
import { checkShowsFolder, dataFolderNames, deleteFile, doesPathExist, getDataFolder, getFileStats, loadShows, readFile, writeFile } from "../utils/files"
import { checkShowsFolder, dataFolderNames, deleteFile, doesPathExist, getDataFolder, getFileStats, loadShows, readFileAsync, writeFile } from "../utils/files"
import { trimShow } from "../utils/responses"

let driveClient: any = null
Expand Down Expand Up @@ -301,7 +301,7 @@ export async function syncDataDrive(data: any) {
let driveFile = await getFile(driveFileId)

let localBiblePath: string = path.resolve(localBiblesFolder, name)
let localFile: string = readFile(localBiblePath)
let localFile: string = await readFileAsync(localBiblePath)

let newest = await getNewest({ driveFile, localPath: localBiblePath })

Expand Down Expand Up @@ -374,14 +374,14 @@ export async function syncDataDrive(data: any) {
syncStates[newest]++

if (newest === "same") {
let showContent = driveContent?.[id] || readFile(localShowPath)
let showContent = driveContent?.[id] || (await readFileAsync(localShowPath))
if (showContent) allShows[id] = showContent
return
}

// get existing content
let cloudContent = driveContent?.[id] ? JSON.stringify([id, driveContent[id]]) : null
let localContent = readFile(localShowPath, "utf8", true)
let localContent = await readFileAsync(localShowPath, "utf8")

// double check with content timestamp
if (newest === "cloud" && cloudContent && localContent) {
Expand Down
14 changes: 6 additions & 8 deletions src/electron/data/import.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { readFileSync } from "fs"
import path, { join } from "path"
// import { pdf } from "pdf-to-img"
import PPTX2Json from "pptx2json"
import protobufjs from "protobufjs"
import SqliteToJson from "sqlite-to-json"
import sqlite3 from "sqlite3"
import WordExtractor from "word-extractor"
import { toApp } from ".."
import { IMPORT } from "../../types/Channels"
import { readFileAsync, readFileBufferAsync } from "../utils/files"

const specialImports: any = {
powerpoint: async (files: string[]) => {
Expand Down Expand Up @@ -62,7 +61,7 @@ const specialImports: any = {
},
songbeamer: async (files: string[], data: any) => {
let encoding = data.encoding.id
let fileContents = await Promise.all(files.map((file) => readFile(file, encoding)))
let fileContents = await Promise.all(files.map(async (file) => await readFile(file, encoding)))
return {
files: fileContents,
length: fileContents.length,
Expand All @@ -85,23 +84,23 @@ export async function importShow(id: any, files: string[] | null, importSettings
if (specialImports[importId]) data = await specialImports[importId](files, importSettings)
else {
// TXT | FreeShow | ProPresenter | VidoePsalm | OpenLP | OpenSong | XML Bible | Lessons.church
data = await Promise.all(files.map((file) => readFile(file)))
data = await Promise.all(files.map(async (file) => await readFile(file)))
}

if (!data.length) return

toApp(IMPORT, { channel: id, data })
}

async function readFile(filePath: string, encoding: BufferEncoding | null = "utf8") {
async function readFile(filePath: string, encoding: BufferEncoding = "utf8") {
let content: string = ""

let name: string = getFileName(filePath) || ""
let extension: string = path.extname(filePath).substring(1).toLowerCase()

try {
if (extension === "pro") content = await decodeProto(filePath)
else content = readFileSync(filePath, encoding).toString()
else content = await readFileAsync(filePath, encoding)
} catch (err) {
console.error("Error reading file:", err.stack)
}
Expand All @@ -122,8 +121,7 @@ async function decodeProto(filePath: string) {

const Presentation = root.lookupType("Presentation")

const fileContent = readFileSync(filePath)
const buffer = Buffer.from(fileContent)
const buffer = await readFileBufferAsync(filePath)
const message = Presentation.decode(buffer)

return JSON.stringify(message)
Expand Down
46 changes: 32 additions & 14 deletions src/electron/data/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import path from "path"
import { STORE } from "../../types/Channels"
import { dataFolderNames, deleteFile, doesPathExist, readFile } from "../utils/files"
import { defaultConfig, defaultSettings, defaultSyncedSettings } from "./defaults"
import { statSync } from "original-fs"

const fileNames: { [key: string]: string } = {
error_log: "error_log",
Expand Down Expand Up @@ -36,21 +37,37 @@ if (process.env.FS_MOCK_STORE_PATH != undefined) {
// MAIN WINDOW
export const config = new Store<any>({ defaults: defaultConfig, ...storeExtraConfig })

// Check that files are parsed properly!
let dataPath = config.path
Object.values(fileNames).forEach((fileName) => {
let p = path.join(path.dirname(dataPath), fileName + ".json")
if (!doesPathExist(p)) return
let jsonData = readFile(p)

try {
JSON.parse(jsonData)
} catch (err) {
console.error("Could not read the " + fileName + ".json settings file, probably wrong JSON format!", err)
// auto delete files that can't be parsed!
deleteFile(p)
}
})
checkStores(dataPath)

// Check that files are parsed properly!
function checkStores(dataPath: string) {
Object.values(fileNames).forEach((fileName) => {
let p = path.join(path.dirname(dataPath), fileName + ".json")
if (!doesPathExist(p)) return

// delete if too large
if (fileName === "history") {
const MAX_BYTES = 30 * 1024 * 1024 // 30 MB
let stats = statSync(p)
if (stats.size > MAX_BYTES) {
deleteFile(p)
console.log(`DELETED ${fileName + ".json"} file as it exceeded 30 MB!`)
return
}
}

let jsonData = readFile(p)

try {
JSON.parse(jsonData)
} catch (err) {
console.error("Could not read the " + fileName + ".json settings file, probably wrong JSON format!", err)
// auto delete files that can't be parsed!
deleteFile(p)
}
})
}

// ERROR LOG
export const error_log = new Store({ name: fileNames.error_log, defaults: {}, ...storeExtraConfig })
Expand Down Expand Up @@ -135,6 +152,7 @@ export function updateDataPath({ reset, dataPath, load }: any = {}) {
if (!userDataPath) return

userDataPath = path.join(userDataPath, dataFolderNames.userData)
checkStores(path.join(userDataPath, "config.json"))
updateStoresPath(load)
}

Expand Down
4 changes: 3 additions & 1 deletion src/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ export async function exitApp() {
app.quit()

// shouldn't need to use exit!
app.exit()
setTimeout(() => {
app.exit()
}, 500)
} catch (err) {
console.error("Failed closing app:", err)
}
Expand Down
2 changes: 2 additions & 0 deletions src/electron/output/helpers/OutputLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { outputOptions } from "../../utils/windowOptions"
import { OutputHelper } from "../OutputHelper"
import { OUTPUT } from "../../../types/Channels"
import { CaptureHelper } from "../../capture/CaptureHelper"
import { wait } from "../../utils/helpers"

export class OutputLifecycle {
static async createOutput(output: Output) {
Expand Down Expand Up @@ -118,6 +119,7 @@ export class OutputLifecycle {
// this has to be called to actually remove the process!
output?.window?.removeAllListeners("close")
output?.window?.close()
await wait(80)
} catch (error) {
console.log(error)
}
Expand Down
5 changes: 5 additions & 0 deletions src/electron/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const app = express()
let servers: any = {}
const DEFAULT_PORTS = { WebSocket: 5505, REST: 5506 }

// WebSocket on 5506, REST on +1 (5506), but works with 5505 as well!
export function startWebSocketAndRest(port: number | undefined) {
startRestListener(port ? port + 1 : 0)
startWebSocket(port)
Expand Down Expand Up @@ -74,7 +75,11 @@ export function startRestListener(PORT: number | undefined) {

app.use(express.json())
app.post("/", (req) => {
// {action: ACTION_ID, ...{}}
let data = req.body
// ?action=ACTION_ID&data={}
if (!data.action && req.query.action) data = { action: req.query.action, ...JSON.parse((req.query.data || "{}") as string) }

receivedData(data, (msg: string) => console.log(`REST: ${msg}`))
})
}
Expand Down
92 changes: 69 additions & 23 deletions src/electron/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,33 @@ export function readFolder(path: string): string[] {
}
}

export async function readFileAsync(path: string, encoding: BufferEncoding = "utf8"): Promise<string> {
return new Promise((resolve) =>
fs.readFile(path, (err, buffer) => {
if (err) console.error(err)
resolve(err ? "" : buffer.toString(encoding))
})
)
}

export async function readFileBufferAsync(path: string): Promise<Buffer> {
return new Promise((resolve) =>
fs.readFile(path, (err, buffer) => {
if (err) console.error(err)
resolve(err ? Buffer.of(0) : buffer)
})
)
}

export async function readFolderAsync(path: string): Promise<string[]> {
return new Promise((resolve) =>
fs.readdir(path, (err, files) => {
if (err) console.error(err)
resolve(err ? [] : files)
})
)
}

export function writeFile(path: string, content: string | NodeJS.ArrayBufferView, id: string = "") {
// don't know if it's necessary to check the file
if (fileContentMatches(content, path)) return
Expand Down Expand Up @@ -369,50 +396,59 @@ export function readExifData({ id }: any, e: any) {
}
}

//////

// SEARCH FOR MEDIA FILE (in drawer media folders & their following folders)
const NESTED_SEARCH = 8 // folder levels deep
export function locateMediaFile({ fileName, splittedPath, folders, ref }: any) {
locateMediaFileAsync({ fileName, splittedPath, folders, ref })
}

async function locateMediaFileAsync({ fileName, splittedPath, folders, ref }: any) {
let matches: string[] = []

findMatches()
await findMatches()
if (!matches.length) return

toApp(MAIN, { channel: "LOCATE_MEDIA_FILE", data: { path: matches[0], ref } })

/////

function findMatches() {
async function findMatches() {
for (const folderPath of folders) {
// if (matches.length > 1) return // this might be used if we want the user to choose if more than one match is found
if (matches.length) return
searchInFolder(folderPath)
await searchInFolder(folderPath)
}
}

function searchInFolder(folderPath: string, level: number = 1) {
async function searchInFolder(folderPath: string, level: number = 1) {
if (level > NESTED_SEARCH || matches.length) return

let currentFolderFolders: string[] = []
let files = readFolder(folderPath)
for (const name of files) {
let currentFilePath: string = path.join(folderPath, name)
let fileStat = getFileStats(currentFilePath)

if (fileStat?.folder) {
// search all files in current folder before searching in any nested folders
currentFolderFolders.push(currentFilePath)
} else {
checkFileForMatch(name, folderPath)
if (matches.length) return
}
}
let files = await readFolderAsync(folderPath)

await Promise.all(
files.map(async (name) => {
let currentFilePath: string = path.join(folderPath, name)
let isFolder = await checkIsFolder(currentFilePath)

if (isFolder) {
// search all files in current folder before searching in any nested folders
currentFolderFolders.push(currentFilePath)
} else {
checkFileForMatch(name, folderPath)
if (matches.length) return
}
})
)

if (matches.length) return

for (const folderName of currentFolderFolders) {
searchInFolder(folderName, level + 1)
if (matches.length) return
}
await Promise.all(
currentFolderFolders.map(async (folderName) => {
await searchInFolder(folderName, level + 1)
if (matches.length) return
})
)
}

function checkFileForMatch(currentFileName: string, folderPath: string) {
Expand All @@ -437,6 +473,16 @@ export function locateMediaFile({ fileName, splittedPath, folders, ref }: any) {
}
}

async function checkIsFolder(path: string): Promise<boolean> {
return new Promise((resolve) =>
fs.stat(path, (err, stats) => {
resolve(err ? false : stats.isDirectory())
})
)
}

//////

// BUNDLE MEDIA FILES FROM ALL SHOWS (IMAGE/VIDEO/AUDIO)
let currentlyBundling: boolean = false
export function bundleMediaFiles({ showsPath, dataPath }: { showsPath: string; dataPath: string }) {
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/components/draw/DrawSettings.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { drawSettings, drawTool } from "../../stores"
import { drawSettings, drawTool, paintCache } from "../../stores"
import Icon from "../helpers/Icon.svelte"
import T from "../helpers/T.svelte"
import { clone } from "../helpers/array"
Expand Down Expand Up @@ -113,7 +113,7 @@

<div class="bottom">
{#if $drawTool === "paint"}
<Button style="flex: 1;padding: 10px;" on:click={() => update("clear", true)} dark center>
<Button style="flex: 1;padding: 10px;" on:click={() => update("clear", true)} disabled={!$paintCache?.length} red={!!$paintCache?.length} dark center>
<Icon id="clear" size={2} right />
<T id="clear.drawing" />
</Button>
Expand Down
Loading

0 comments on commit cd37688

Please sign in to comment.