index.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. /**
  2. * LiveMe Pro Tools
  3. */
  4. const appName = 'LiveMe Pro Tools'
  5. const { app, BrowserWindow, ipcMain, Menu, shell, dialog } = require('electron')
  6. const { exec } = require('child_process')
  7. const os = require('os')
  8. const fs = require('fs')
  9. const path = require('path')
  10. const request = require('request')
  11. const tarfs = require('tar-fs')
  12. const DataManager = new (require('./datamanager').DataManager)()
  13. const LivemeAPI = require('./livemeapi')
  14. const LiveMe = new LivemeAPI({})
  15. const isDev = require('electron-is-dev')
  16. const ffmpeg = require('fluent-ffmpeg')
  17. const async = require('async')
  18. let mainWindow = null
  19. let playerWindow = null
  20. let bookmarksWindow = null
  21. let chatWindow = null
  22. let wizardWindow = null
  23. let menu = null
  24. let appSettings = require('electron-settings')
  25. function createWindow () {
  26. let isFreshInstall = appSettings.get('general.fresh_install') == null
  27. if (isFreshInstall === true) {
  28. appSettings.set('general', {
  29. fresh_install: true,
  30. playerpath: '',
  31. hide_zeroreplay_fans: false,
  32. hide_zeroreplay_followings: true
  33. })
  34. appSettings.set('position', {
  35. mainWindow: [-1, -1],
  36. playerWindow: [-1, -1],
  37. bookmarksWindow: [-1, -1],
  38. fansWindow: [-1, -1],
  39. followingsWindow: [-1, -1]
  40. })
  41. appSettings.set('size', {
  42. mainWindow: [1024, 600],
  43. playerWindow: [370, 680],
  44. bookmarksWindow: [400, 720]
  45. })
  46. appSettings.set('downloads', {
  47. path: path.join(app.getPath('home'), 'Downloads'),
  48. template: '%%replayid%%',
  49. chunkthreads: 1,
  50. ffmpegquality: 1
  51. })
  52. appSettings.set('lamd', {
  53. enabled: false,
  54. url: 'http://localhost:8280',
  55. handle_downloads: false
  56. })
  57. }
  58. if (!appSettings.get('downloads.path')) {
  59. appSettings.set('downloads', {
  60. path: path.join(app.getPath('home'), 'Downloads'),
  61. template: '%%replayid%%',
  62. chunkthreads: 1,
  63. ffmpegquality: 1
  64. })
  65. }
  66. if (!appSettings.get('downloads.chunks')) {
  67. appSettings.set('downloads.chunks', 1)
  68. }
  69. if (!appSettings.get('lamd.enabled')) {
  70. appSettings.set('lamd', {
  71. enabled: false,
  72. url: 'http://localhost:8280',
  73. handle_downloads: false
  74. })
  75. }
  76. if (!appSettings.get('history.viewed_maxage')) {
  77. appSettings.set('history', {
  78. viewed_maxage: 1
  79. })
  80. }
  81. let test = appSettings.get('position')
  82. if (test.mainWindow[1] === undefined) {
  83. appSettings.set('position', {
  84. mainWindow: [-1, -1],
  85. playerWindow: [-1, -1],
  86. bookmarksWindow: [-1, -1]
  87. })
  88. }
  89. /**
  90. * Create our window definitions
  91. */
  92. // let winposition = appSettings.get('position.mainWindow')
  93. let winsize = appSettings.get('size.mainWindow')
  94. mainWindow = new BrowserWindow({
  95. icon: path.join(__dirname, 'appicon.ico'),
  96. width: winsize[0],
  97. height: winsize[1],
  98. minWidth: 1024,
  99. maxWidth: 1024,
  100. minHeight: 480,
  101. maxHeight: 1200,
  102. autoHideMenuBar: true,
  103. disableAutoHideCursor: true,
  104. titleBarStyle: 'default',
  105. fullscreen: false,
  106. maximizable: false,
  107. frame: false,
  108. show: false,
  109. backgroundColor: '#000000',
  110. webPreferences: {
  111. webSecurity: false,
  112. textAreasAreResizable: false,
  113. plugins: true
  114. }
  115. })
  116. wizardWindow = new BrowserWindow({
  117. icon: path.join(__dirname, 'appicon.ico'),
  118. width: 520,
  119. height: 300,
  120. darkTheme: true,
  121. autoHideMenuBar: false,
  122. disableAutoHideCursor: true,
  123. titleBarStyle: 'default',
  124. resizable: false,
  125. fullscreen: false,
  126. maximizable: false,
  127. show: false,
  128. frame: false,
  129. backgroundColor: 'transparent',
  130. webPreferences: {
  131. webSecurity: false,
  132. textAreasAreResizable: false,
  133. plugins: true
  134. }
  135. })
  136. /**
  137. * Configure our window contents and callbacks
  138. */
  139. mainWindow.loadURL(`file://${__dirname}/app/index.html`)
  140. mainWindow
  141. .on('open', () => {})
  142. .on('close', () => {
  143. appSettings.set('position.mainWindow', mainWindow.getPosition())
  144. appSettings.set('size.mainWindow', mainWindow.getSize())
  145. DataManager.saveToDisk()
  146. if (playerWindow != null) {
  147. playerWindow.close()
  148. }
  149. if (bookmarksWindow != null) {
  150. bookmarksWindow.close()
  151. }
  152. if (chatWindow != null) {
  153. chatWindow.close()
  154. }
  155. mainWindow.webContents.session.clearCache(() => {
  156. // Purge the cache to help avoid eating up space on the drive
  157. })
  158. mainWindow = null
  159. setTimeout(() => {
  160. app.quit()
  161. }, 500)
  162. })
  163. wizardWindow.on('close', () => {
  164. wizardWindow.webContents.session.clearCache(() => {
  165. // Purge the cache to help avoid eating up space on the drive
  166. })
  167. if (mainWindow != null) {
  168. setTimeout(() => {
  169. let pos = appSettings.get('position.mainWindow')
  170. mainWindow.setPosition(pos[0], pos[1], false)
  171. mainWindow.show()
  172. }, 250)
  173. }
  174. wizardWindow = null
  175. })
  176. /**
  177. * Build our application menus using the templates provided
  178. * further down.
  179. */
  180. menu = Menu.buildFromTemplate(getMenuTemplate())
  181. Menu.setApplicationMenu(menu)
  182. global.isDev = isDev
  183. global.LiveMe = LiveMe
  184. global.DataManager = DataManager
  185. DataManager.loadFromDisk()
  186. setTimeout(() => {
  187. const dt = new Date()
  188. let ma = appSettings.get('history.viewed_maxage')
  189. let od = Math.floor((dt.getTime() - (ma * 86400000)) / 1000)
  190. DataManager.unviewProfiles(od, false)
  191. }, 250)
  192. if (isFreshInstall) {
  193. DataManager.disableWrites()
  194. wizardWindow.loadURL(`file://${__dirname}/app/wizard.html`)
  195. wizardWindow.show()
  196. } else {
  197. mainWindow.show()
  198. let pos = appSettings.get('position.mainWindow').length > 1 ? appSettings.get('position.mainWindow') : [null, null]
  199. if (pos[0] != null) {
  200. mainWindow.setPosition(pos[0], pos[1], false)
  201. }
  202. }
  203. }
  204. let shouldQuit = app.makeSingleInstance(function (commandLine, workingDirectory) {
  205. if (mainWindow) {
  206. mainWindow.focus()
  207. }
  208. })
  209. if (shouldQuit) {
  210. app.quit()
  211. process.exit()
  212. }
  213. app.on('ready', () => {
  214. createWindow()
  215. })
  216. app.on('window-all-closed', () => {
  217. app.quit()
  218. })
  219. app.on('activate', () => {
  220. if (mainWindow === null) {
  221. createWindow()
  222. }
  223. })
  224. /**
  225. * IPC Event Handlers
  226. */
  227. ipcMain.on('import-queue', (event, arg) => {})
  228. ipcMain.on('import-users', (event, arg) => {})
  229. ipcMain.on('export-users', (event, arg) => {})
  230. ipcMain.on('downloads-parallel', (event, arg) => {
  231. dlQueue.concurrency = arg
  232. })
  233. ipcMain.on('download-replay', (event, arg) => {
  234. DataManager.addToQueueList(arg.videoid)
  235. dlQueue.push(arg.videoid, err => {
  236. if (err) {
  237. mainWindow.webContents.send('download-error', err)
  238. } else {
  239. mainWindow.webContents.send('download-complete', { videoid: arg.videoid })
  240. }
  241. })
  242. })
  243. /**
  244. * Cannot cancel active download, only remove queued entries.
  245. */
  246. ipcMain.on('download-cancel', (event, arg) => {
  247. dlQueue.remove(function (task) {
  248. if (task.data === arg.videoid) {
  249. DataManager.removeFromQueueList(task.data)
  250. return true
  251. }
  252. return false
  253. })
  254. })
  255. /**
  256. * It is done this way in case the API call to jDownloader returns an error or doesn't connect.
  257. */
  258. const dlQueue = async.queue((task, done) => {
  259. // Set custom FFMPEG path if defined
  260. if (appSettings.get('downloads.ffmpeg')) ffmpeg.setFfmpegPath(appSettings.get('downloads.ffmpeg'))
  261. // Get video info
  262. LiveMe.getVideoInfo(task).then(video => {
  263. const path = appSettings.get('downloads.path')
  264. const dt = new Date(video.vtime * 1000)
  265. const mm = dt.getMonth() + 1
  266. const dd = dt.getDate()
  267. let ffmpegOpts = []
  268. let filename = appSettings.get('downloads.template')
  269. .replace(/%%broadcaster%%/g, video.uname)
  270. .replace(/%%longid%%/g, video.userid)
  271. .replace(/%%replayid%%/g, video.vid)
  272. .replace(/%%replayviews%%/g, video.playnumber)
  273. .replace(/%%replaylikes%%/g, video.likenum)
  274. .replace(/%%replayshares%%/g, video.sharenum)
  275. .replace(/%%replaytitle%%/g, video.title ? video.title : 'untitled')
  276. .replace(/%%replayduration%%/g, video.videolength)
  277. .replace(/%%replaydatepacked%%/g, (dt.getFullYear() + (mm < 10 ? '0' : '') + mm + (dd < 10 ? '0' : '') + dd))
  278. .replace(/%%replaydateus%%/g, ((mm < 10 ? '0' : '') + mm + '-' + (dd < 10 ? '0' : '') + dd + '-' + dt.getFullYear()))
  279. .replace(/%%replaydateeu%%/g, ((dd < 10 ? '0' : '') + dd + '-' + (mm < 10 ? '0' : '') + mm + '-' + dt.getFullYear()))
  280. filename = filename.replace(/[/\\?%*:|"<>]/g, '-')
  281. filename = filename.replace(/([^a-z0-9\s]+)/gi, '-')
  282. filename = filename.replace(/[\u{0080}-\u{FFFF}]/gu, '')
  283. filename += '.mp4'
  284. video._filename = filename
  285. mainWindow.webContents.send('download-start', {
  286. videoid: task,
  287. filename: filename
  288. })
  289. switch (parseInt(appSettings.get('downloads.ffmpegquality'))) {
  290. case 2: // Best
  291. ffmpegOpts = [
  292. '-c:v h264',
  293. '-preset faster',
  294. '-q:v 0',
  295. '-c:a copy',
  296. '-bsf:a aac_adtstoasc',
  297. '-vsync 2',
  298. '-movflags faststart'
  299. ]
  300. break
  301. case 1: // Fast
  302. ffmpegOpts = [
  303. '-c:v h264',
  304. '-preset superfast',
  305. '-q:v 0',
  306. '-c:a copy',
  307. '-bsf:a aac_adtstoasc',
  308. '-vsync 2',
  309. '-movflags faststart'
  310. ]
  311. break
  312. default: // None
  313. ffmpegOpts = [
  314. '-c copy',
  315. '-bsf:a aac_adtstoasc',
  316. '-vsync 2',
  317. '-movflags faststart'
  318. ]
  319. break
  320. }
  321. switch (appSettings.get('downloads.method')) {
  322. case 'chunk':
  323. request(video.hlsvideosource, (err, res, body) => {
  324. if (err || !body) {
  325. fs.writeFileSync(`${path}/${filename}-error.log`, JSON.stringify(err, null, 2))
  326. return done({ videoid: task, error: err || 'Failed to fetch m3u8 file.' })
  327. }
  328. // Separate ts names from m3u8
  329. let concatList = ''
  330. const tsList = []
  331. body.split('\n').forEach(line => {
  332. if (line.indexOf('.ts') !== -1) {
  333. const tsName = line.split('?')[0]
  334. const tsPath = `${path}/lpt_temp/${video.vid}_${tsName}`
  335. // Check if TS has already been added to array
  336. if (concatList.indexOf(tsPath) === -1) {
  337. // We'll use this later to merge downloaded chunks
  338. concatList += `${tsPath}|`
  339. // Push data to list
  340. tsList.push({ name: tsName, path: tsPath })
  341. }
  342. }
  343. })
  344. // remove last |
  345. concatList = concatList.slice(0, -1)
  346. // Check if tmp dir exists
  347. if (!fs.existsSync(`${path}/lpt_temp`)) {
  348. // create temporary dir for ts files
  349. fs.mkdirSync(`${path}/lpt_temp`)
  350. }
  351. // Download chunks
  352. let downloadedChunks = 0
  353. async.eachLimit(tsList, 2, (file, next) => {
  354. const stream = request(`${video.hlsvideosource.split('/').slice(0, -1).join('/')}/${file.name}`)
  355. .on('error', err => {
  356. fs.writeFileSync(`${path}/${filename}-error.log`, JSON.stringify(err, null, 2))
  357. return done({ videoid: task, error: err })
  358. })
  359. .pipe(
  360. fs.createWriteStream(file.path)
  361. )
  362. // Events
  363. stream.on('finish', () => {
  364. downloadedChunks += 1
  365. mainWindow.webContents.send('download-progress', {
  366. videoid: task,
  367. state: `Downloading stream chunks.. (${downloadedChunks}/${tsList.length})`,
  368. percent: Math.round((downloadedChunks / tsList.length) * 100)
  369. })
  370. next()
  371. })
  372. }, () => {
  373. // Chunks downloaded
  374. ffmpeg()
  375. .on('start', c => {
  376. mainWindow.webContents.send('download-progress', {
  377. videoid: task,
  378. state: `Converting to MP4 file, please wait..`,
  379. percent: 0
  380. })
  381. })
  382. .on('progress', function (progress) {
  383. // FFMPEG doesn't always have this >.<
  384. if (!progress.percent) {
  385. progress.percent = ((progress.targetSize * 1000) / +video.videosize) * 100
  386. }
  387. mainWindow.webContents.send('download-progress', {
  388. videoid: task,
  389. state: `Converting to MP4 file (${Math.round(progress.percent)}%)`,
  390. percent: progress.percent
  391. })
  392. })
  393. .on('end', (stdout, stderr) => {
  394. DataManager.addDownloaded(video.vid)
  395. if (appSettings.get('downloads.deltmp')) {
  396. tsList.forEach(file => fs.unlinkSync(file.path))
  397. }
  398. return done()
  399. })
  400. .on('error', (err, stdout, stderr) => {
  401. fs.writeFileSync(`${path}/${filename}-error.log`, JSON.stringify([err, stdout, stderr], null, 2))
  402. if (appSettings.get('downloads.deltmp')) {
  403. //tsList.forEach(file => fs.unlinkSync(file.path))
  404. }
  405. return done({ videoid: task, error: err })
  406. })
  407. .input(`concat:${concatList}`)
  408. .output(`${path}/${filename}`)
  409. .outputOptions(ffmpegOpts)
  410. .run()
  411. })
  412. })
  413. break
  414. case 'ffmpeg':
  415. ffmpeg(video.hlsvideosource)
  416. .outputOptions(ffmpegOpts)
  417. .output(path + '/' + filename)
  418. .on('end', function (stdout, stderr) {
  419. DataManager.addDownloaded(video.vid)
  420. return done()
  421. })
  422. .on('progress', function (progress) {
  423. // FFMPEG doesn't always have this >.<
  424. if (!progress.percent) {
  425. progress.percent = ((progress.targetSize * 1000) / +video.videosize) * 100
  426. }
  427. mainWindow.webContents.send('download-progress', {
  428. videoid: task,
  429. state: `Downloading (${Math.round(progress.percent)}%)`,
  430. percent: progress.percent
  431. })
  432. })
  433. .on('start', function (c) {
  434. console.log('started', c)
  435. mainWindow.webContents.send('download-start', {
  436. videoid: task,
  437. filename: filename
  438. })
  439. })
  440. .on('error', function (err, stdout, stderr) {
  441. fs.writeFileSync(`${path}/${filename}-error.log`, JSON.stringify([err, stdout, stderr], null, 2))
  442. return done({ videoid: task, error: err })
  443. })
  444. .run()
  445. break
  446. }
  447. })
  448. }, +appSettings.get('downloads.parallel') || 3)
  449. /**
  450. * Watch a Replay - Use either internal player or external depending on settings
  451. */
  452. ipcMain.on('watch-replay', (event, arg) => {
  453. DataManager.addWatched(arg.videoid)
  454. LiveMe.getVideoInfo(arg.videoid)
  455. .then(video => {
  456. let internalplayer = appSettings.get('general.playerpath')
  457. let playerpath = internalplayer
  458. if (playerpath.length > 5) {
  459. exec(playerpath.replace('%url%', video.hlsvideosource))
  460. } else {
  461. // Open internal player
  462. if (playerWindow == null) {
  463. let winposition = appSettings.get('position.playerWindow')
  464. let winsize = appSettings.get('size.playerWindow')
  465. playerWindow = new BrowserWindow({
  466. icon: path.join(__dirname, 'appicon.ico'),
  467. width: winsize[0],
  468. height: winsize[1],
  469. x: winposition[0] !== -1 ? winposition[0] : null,
  470. y: winposition[1] !== -1 ? winposition[1] : null,
  471. minWidth: 380,
  472. minHeight: 708,
  473. darkTheme: true,
  474. autoHideMenuBar: false,
  475. disableAutoHideCursor: true,
  476. titleBarStyle: 'default',
  477. fullscreen: false,
  478. maximizable: false,
  479. frame: false,
  480. backgroundColor: '#000000',
  481. webPreferences: {
  482. webSecurity: false,
  483. textAreasAreResizable: false,
  484. plugins: true
  485. }
  486. })
  487. playerWindow.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()))
  488. playerWindow.on('close', () => {
  489. appSettings.set('position.playerWindow', playerWindow.getPosition())
  490. appSettings.set('size.playerWindow', playerWindow.getSize())
  491. playerWindow.webContents.session.clearCache(() => {
  492. // Purge the cache to help avoid eating up space on the drive
  493. })
  494. playerWindow = null
  495. })
  496. }
  497. playerWindow.loadURL(`file://${__dirname}/app/player.html?${video.vid}`)
  498. }
  499. })
  500. .catch(err => {
  501. console.log('[watch-replay] getVideoInfo Error:', err)
  502. })
  503. })
  504. ipcMain.on('open-bookmarks', (event, arg) => {})
  505. ipcMain.on('show-user', (event, arg) => {
  506. mainWindow.webContents.send('show-user', { userid: arg.userid })
  507. })
  508. ipcMain.on('open-followings-window', (event, arg) => {
  509. let winposition = appSettings.get('position.followingsWindow') ? appSettings.get('position.followingsWindow') : [-1, -1]
  510. let win = new BrowserWindow({
  511. x: winposition[0] !== -1 ? winposition[0] : null,
  512. y: winposition[1] !== -1 ? winposition[1] : null,
  513. width: 420,
  514. height: 720,
  515. resizable: false,
  516. darkTheme: false,
  517. autoHideMenuBar: true,
  518. skipTaskbar: false,
  519. backgroundColor: '#000000',
  520. disableAutoHideCursor: true,
  521. titleBarStyle: 'default',
  522. fullscreen: false,
  523. maximizable: false,
  524. closable: true,
  525. frame: false,
  526. show: false
  527. })
  528. win.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()))
  529. win.on('ready-to-show', () => {
  530. win.show()
  531. }).on('close', () => {
  532. appSettings.set('position.followingsWindow', win.getPosition())
  533. }).loadURL(`file://${__dirname}/app/listwindow.html?1&` + arg.userid)
  534. })
  535. ipcMain.on('open-followers-window', (event, arg) => {
  536. let winposition = appSettings.get('position.fansWindow') ? appSettings.get('position.fansWindow') : [-1, -1]
  537. var win = new BrowserWindow({
  538. x: winposition[0] !== -1 ? winposition[0] : null,
  539. y: winposition[1] !== -1 ? winposition[1] : null,
  540. width: 420,
  541. height: 720,
  542. resizable: false,
  543. darkTheme: false,
  544. autoHideMenuBar: true,
  545. skipTaskbar: false,
  546. backgroundColor: '#000000',
  547. disableAutoHideCursor: true,
  548. titleBarStyle: 'default',
  549. fullscreen: false,
  550. maximizable: false,
  551. closable: true,
  552. frame: false,
  553. show: false
  554. })
  555. win.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()))
  556. win.on('ready-to-show', () => {
  557. win.show()
  558. }).on('close', () => {
  559. appSettings.set('position.fansWindow', win.getPosition())
  560. }).loadURL(`file://${__dirname}/app/listwindow.html?0&` + arg.userid)
  561. })
  562. ipcMain.on('read-comments', (event, arg) => {
  563. let win = new BrowserWindow({
  564. width: 400,
  565. height: 660,
  566. resizable: false,
  567. darkTheme: false,
  568. autoHideMenuBar: true,
  569. skipTaskbar: false,
  570. backgroundColor: '#000000',
  571. disableAutoHideCursor: true,
  572. titleBarStyle: 'default',
  573. fullscreen: false,
  574. maximizable: false,
  575. closable: true,
  576. frame: false,
  577. show: false
  578. })
  579. win.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()))
  580. win.on('ready-to-show', () => {
  581. win.showInactive()
  582. }).loadURL(`file://${__dirname}/app/comments.html?` + arg.userid)
  583. })
  584. ipcMain.on('open-bookmarks', (event, arg) => {
  585. if (bookmarksWindow == null) {
  586. let winposition = appSettings.get('position.bookmarksWindow')
  587. let winsize = appSettings.get('size.bookmarksWindow')
  588. bookmarksWindow = new BrowserWindow({
  589. x: winposition[0] > -1 ? winposition[0] : null,
  590. y: winposition[1] > -1 ? winposition[1] : null,
  591. width: 400,
  592. height: winsize[1],
  593. minWidth: 400,
  594. maxWidth: 400,
  595. minHeight: 480,
  596. maxHeight: 1200,
  597. darkTheme: true,
  598. autoHideMenuBar: false,
  599. disableAutoHideCursor: true,
  600. titleBarStyle: 'default',
  601. fullscreen: false,
  602. maximizable: false,
  603. frame: false,
  604. show: false,
  605. backgroundColor: '#000000'
  606. })
  607. bookmarksWindow.setMenu(Menu.buildFromTemplate(getMiniMenuTemplate()))
  608. bookmarksWindow.on('close', () => {
  609. appSettings.set('position.bookmarksWindow', bookmarksWindow.getPosition())
  610. appSettings.set('size.bookmarksWindow', bookmarksWindow.getSize())
  611. bookmarksWindow.webContents.session.clearCache(() => {
  612. // Purge the cache to help avoid eating up space on the drive
  613. })
  614. bookmarksWindow = null
  615. })
  616. } else {
  617. bookmarksWindow.restore()
  618. }
  619. bookmarksWindow.on('ready-to-show', () => {
  620. bookmarksWindow.show()
  621. }).loadURL(`file://${__dirname}/app/bookmarks.html`)
  622. })
  623. ipcMain.on('restore-backup', (event, arg) => {
  624. dialog.showOpenDialog(
  625. {
  626. properties: [
  627. 'openFile'
  628. ],
  629. buttonLabel: 'Restore',
  630. filters: [
  631. {
  632. name: 'TAR files',
  633. extensions: ['tar']
  634. }
  635. ]
  636. },
  637. (filePath) => {
  638. if (filePath != null) {
  639. mainWindow.webContents.send('shutdown')
  640. DataManager.disableWrites()
  641. let configPath = path.join(app.getPath('appData'), app.getName(), '/')
  642. fs.createReadStream(filePath[0]).pipe(tarfs.extract(configPath))
  643. setTimeout(() => {
  644. app.relaunch()
  645. app.quit()
  646. }, 1000)
  647. }
  648. }
  649. )
  650. })
  651. ipcMain.on('create-backup', (event, arg) => {
  652. let configPath = path.join(app.getPath('appData'), app.getName())
  653. let backupFile = path.join(app.getPath('home'), 'Downloads', 'liveme-pro-tools-backup.tar')
  654. tarfs.pack(
  655. configPath,
  656. {
  657. entries: [ 'bookmarks.json', 'downloaded.json', 'profiles.json', 'watched.json' ]
  658. }
  659. ).pipe(fs.createWriteStream(backupFile))
  660. })
  661. function getMenuTemplate () {
  662. let template = [
  663. {
  664. label: 'Edit',
  665. submenu: [
  666. { role: 'undo' },
  667. { role: 'redo' },
  668. { type: 'separator' },
  669. { role: 'cut' },
  670. { role: 'copy' },
  671. { role: 'paste' },
  672. { role: 'delete' },
  673. { role: 'selectall' }
  674. ]
  675. },
  676. {
  677. role: 'window',
  678. submenu: [
  679. { role: 'minimize' },
  680. { role: 'close' },
  681. { type: 'separator' },
  682. {
  683. label: 'Developer Tools',
  684. submenu: [
  685. { role: 'reload' },
  686. { role: 'forcereload' },
  687. { role: 'toggledevtools' }
  688. ]
  689. }
  690. ]
  691. },
  692. {
  693. role: 'help',
  694. submenu: [
  695. {
  696. label: 'LiveMe Pro Tools Page',
  697. click: () => shell.openExternal('https://github.com/lewdninja/liveme-pro-tools/')
  698. }
  699. ]
  700. }
  701. ]
  702. /**
  703. * This is here in case macOS version gets added back end after all the bugs/issues are figured out.
  704. * Requires a contributor running macOS now.
  705. */
  706. if (process.platform === 'darwin') {
  707. template.unshift({
  708. label: appName,
  709. submenu: [
  710. {
  711. label: 'About ' + appName,
  712. click: () => {}
  713. },
  714. { type: 'separator' },
  715. { role: 'services', submenu: [] },
  716. { type: 'separator' },
  717. { role: 'hide' },
  718. { role: 'hideothers' },
  719. { role: 'unhide' },
  720. { type: 'separator' },
  721. {
  722. label: 'Quit ' + appName,
  723. accelerator: 'CommandOrControl+Q',
  724. click: () => { mainWindow.close() }
  725. }
  726. ]
  727. })
  728. }
  729. return template
  730. }
  731. function getMiniMenuTemplate () {
  732. let template = [
  733. {
  734. label: 'Edit',
  735. submenu: [
  736. { role: 'undo' },
  737. { role: 'redo' },
  738. { type: 'separator' },
  739. { role: 'cut' },
  740. { role: 'copy' },
  741. { role: 'paste' },
  742. { role: 'delete' },
  743. { role: 'selectall' }
  744. ]
  745. },
  746. {
  747. role: 'window',
  748. submenu: [
  749. { role: 'minimize' },
  750. { role: 'close' },
  751. { type: 'separator' },
  752. {
  753. label: 'Developer Tools',
  754. submenu: [
  755. { role: 'reload' },
  756. { role: 'forcereload' },
  757. { role: 'toggledevtools' }
  758. ]
  759. }
  760. ]
  761. }
  762. ]
  763. return template
  764. }