OverviewFilesCommitsIssuesDocsReleasesPeers

keet-deobfuscated / @holepunchto / keet-store / store / media / file / saga / desktop-file-downloads.saga.js

185 lines · javascript ref:
import e, { promises as o } from "fs"
import t from "path"
import d from "child_process"
import {
  select as n,
  take as i,
  takeLeading as r,
  takeEvery as l,
  fork as s,
  put as c,
  all as a,
  call as y,
  race as f,
  delay as p,
} from "redux-saga/effects"
import m from "pear-user-dirs"
import {
  addFileDownloadCmd as u,
  resumeFileDownload as w,
  updateFileDownloadName as h,
  updateFileDownloadProgress as I,
  updateFileDownloadSpeed as g,
  removeFileDownload as k,
  getFileDownload as D,
  getFileDownloadsPaused as L,
  setFileDownloadError as S,
  getFileDownloadsOpen as $,
  toggleFileDownloads as x,
  getFileEntryById as E,
  addFileListDownloadCmd as N,
  getFileDownloadsNextId as b,
  savePendingDownloadedFiles as A,
} from ".."
import {
  NOTIFICATION_CODE as P,
  userCustomNotification as B,
  userWarnNotification as O,
} from "../../../notification"
import { getUIAppActive as W } from "../../../ui-desktop"
import {
  getPreferencesNotificationsAlerts as _,
  NOTIFICATIONS_ALERT_TYPES as j,
} from "../../../preferences"
import { openFileDownloadFolder as v } from "../file.action"
const C = 2097152,
  F = 50,
  H = (e) => (o) => w.match(o) && o?.payload === e
export function* checkAccess(e) {
  try {
    return (yield y([o, o.access], e, o.constants.W_OK), !0)
  } catch {
    return !1
  }
}
function U(o, d) {
  let n = d,
    i = 0
  for (; e.existsSync(t.join(o, n)); ) {
    i += 1
    const e = d.split(".")
    if (e.length > 1) {
      const o = e.pop()
      n = `${e.join(".")} (${i}).${o}`
    } else n = `${d} (${i})`
  }
  return n
}
export function* desktopFileDownloadsSaga() {
  const o = {},
    y = (yield m()).downloads
  function* w({ downloadId: d, name: r, httpLink: l, byteLength: s }) {
    let m, u
    try {
      if (!(yield checkAccess(y)))
        return void (yield a([
          c(S({ downloadId: d, error: "No access" })),
          c(
            O({
              title: "No Access",
              message: "Please check the write permissions on your Downloads folder",
            }),
          ),
        ]))
      const w = yield fetch(l)
      m = yield w.body.getReader()
      const k = U(y, r)
      yield c(h({ downloadId: d, fileName: k }))
      const E = t.join(y, k)
      ;((u = e.createWriteStream(E, { highWaterMark: C })),
        u.on("error", (e) => {
          throw (o[d]?.cancel(), new Error("Could not write to file", { cause: e }))
        }))
      let N = 0,
        b = 0,
        A = Date.now(),
        v = 0
      for (;;) {
        ;(yield n((e) => L(e, d))) && (yield c(g({ downloadId: d, speed: 0 })), yield i(H(d)))
        const { result: e, timeout: o } = yield f({ result: m.read(), timeout: p(3e3) })
        if (o) {
          const { speed: e } = yield n((e) => D(e, d))
          e > 0 && (yield c(g({ downloadId: d, speed: 0 })))
          continue
        }
        const { value: t, done: r } = e || {}
        if (r) {
          const e = yield n(W),
            o = yield n(_)
          ;((yield n($)) || (yield c(x(!0))),
            yield c(g({ downloadId: d, speed: 0 })),
            yield c(
              B({
                appActive: e,
                code: P.FILE_DOWNLOAD_FINISHED,
                fileName: k,
                notifyByBanner: !1,
                notifyByPush: o.includes(j.PUSH),
                notifyBySound: o.includes(j.SOUND),
              }),
            ))
          break
        }
        ;((N += t.length),
          (v += 1),
          u.write(t) ||
            (yield new Promise((e) => {
              u.once("drain", e)
            })),
          (v % F === 0 || N >= s) && (yield c(I({ downloadId: d, byteProgress: N }))))
        const l = Date.now()
        if (l - A >= 1e3) {
          const e = (l - A) / 1e3,
            o = N - b
          ;((A = l), (b = N))
          const t = o / e
          yield c(g({ downloadId: d, speed: t }))
        }
      }
    } catch (e) {
      ;(console.error(e), yield c(S({ downloadId: d, error: e.message })))
    } finally {
      ;(u?.errored && (yield c(S({ downloadId: d, error: "Could not write to file" }))),
        m?.cancel(),
        u?.end(),
        delete o[d])
    }
  }
  ;(yield l([u, N], function* (e) {
    let t = []
    const d = yield n(b)
    if (u.match(e)) t = [e.payload.fileId]
    else {
      if (!N.match(e)) return
      t = e.payload.fileIds
    }
    if (0 === t.length) return
    let i = d
    const r = []
    for (const e of t) {
      const o = yield n(E, e)
      o && (r.push({ fileId: e, fileEntry: o, downloadId: i }), (i += 1))
    }
    yield c(A({ files: r, roomId: e.payload.roomId, lastDownloadId: i }))
    for (const e of r)
      e.downloadId in o ||
        (o[e.downloadId] = yield s(w, {
          downloadId: e.downloadId,
          name: e.fileEntry.name,
          httpLink: e.fileEntry.httpLink,
          byteLength: e.fileEntry.byteLength,
        }))
  }),
    yield r(v, function* () {
      try {
        const e = { darwin: "open", win32: "explorer" }[process.platform] || "xdg-open"
        yield d.execSync(`${e} "${y}"`)
      } catch (e) {
        console.error(`Can't open ${y}`, e)
      }
    }),
    yield r(k, function ({ payload: e }) {
      ;(o[e]?.cancel(), delete o[e])
    }))
}