« get me outta code hell

mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--downloaders.js153
-rw-r--r--list-cache.js33
-rw-r--r--package-lock.json9
-rw-r--r--package.json1
-rw-r--r--todo.txt1
5 files changed, 86 insertions, 111 deletions
diff --git a/downloaders.js b/downloaders.js
index 4b4750c..4c1ce38 100644
--- a/downloaders.js
+++ b/downloaders.js
@@ -1,7 +1,7 @@
 const { promisifyProcess } = require('./general-util')
 const { promisify } = require('util')
 const { spawn } = require('child_process')
-const { Base64 } = require('js-base64')
+const { URL } = require('url')
 const mkdirp = promisify(require('mkdirp'))
 const fs = require('fs')
 const fetch = require('node-fetch')
@@ -31,7 +31,9 @@ const copyFile = (source, target) => {
   })
 }
 
-const cachify = (identifier, baseFunction) => {
+const disableBackResolving = arg => arg.split('/').map(str => str.replace(/^\../, '_..')).join('/')
+
+const cachify = (identifier, keyFunction, baseFunction) => {
   return async arg => {
     // If there was no argument passed (or it aws empty), nothing will work..
     if (!arg) {
@@ -41,7 +43,7 @@ const cachify = (identifier, baseFunction) => {
     // Determine where the final file will end up. This is just a directory -
     // the file's own name is determined by the downloader.
     const cacheDir = downloaders.rootCacheDir + '/' + identifier
-    const finalDirectory = cacheDir + '/' + Base64.encode(arg)
+    const finalDirectory = cacheDir + '/' + disableBackResolving(keyFunction(arg))
 
     // Check if that directory only exists. If it does, return the file in it,
     // because it being there means we've already downloaded it at some point
@@ -105,73 +107,84 @@ const downloaders = {
   // TODO: Cross-platform stuff
   rootCacheDir: process.env.HOME + '/.mtui/downloads',
 
-  http: cachify('http', arg => {
-    const out = (
-      tempy.directory() + '/' +
-      sanitize(decodeURIComponent(path.basename(arg))))
-
-    return fetch(arg)
-      .then(response => response.buffer())
-      .then(buffer => writeFile(out, buffer))
-      .then(() => out)
-  }),
-
-  youtubedl: cachify('youtubedl', arg => {
-    const outDir = tempy.directory()
-    const outFile = outDir + '/%(id)s-%(uploader)s-%(title)s.%(ext)s'
-
-    const opts = [
-      '--quiet',
-      '--no-warnings',
-      '--extract-audio',
-      '--audio-format', downloaders.extension,
-      '--output', outFile,
-      arg
-    ]
-
-    return promisifyProcess(spawn('youtube-dl', opts))
-      .then(() => readdir(outDir))
-      .then(files => outDir + '/' + files[0])
-  }),
-
-  local: cachify('local', arg => {
-    // Usually we'd just return the given argument in a local
-    // downloader, which is efficient, since there's no need to
-    // copy a file from one place on the hard drive to another.
-    // But reading from a separate drive (e.g. a USB stick or a
-    // CD) can take a lot longer than reading directly from the
-    // computer's own drive, so this downloader copies the file
-    // to a temporary file on the computer's drive.
-    // Ideally, we'd be able to check whether a file is on the
-    // computer's main drive mount or not before going through
-    // the steps to copy, but I'm not sure if there's a way to
-    // do that (and it's even less likely there'd be a cross-
-    // platform way).
-
-    // It's possible the downloader argument starts with the "file://"
-    // protocol string; in that case we'll want to snip it off and URL-
-    // decode the string.
-    arg = removeFileProtocol(arg)
-
-    // TODO: Is it necessary to sanitize here?
-    // Haha, the answer to "should I sanitize" is probably always YES..
-    const base = path.basename(arg, path.extname(arg))
-    const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
-
-    return copyFile(arg, out)
-      .then(() => out)
-  }),
-
-  locallink: cachify('locallink', arg => {
-    // Like the local downloader, but creates a symbolic link to the argument.
-
-    arg = removeFileProtocol(arg)
-    const base = path.basename(arg, path.extname(arg))
-    const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
-
-    return symlink(path.resolve(arg), out)
-      .then(() => out)
-  }),
+  http: cachify('http',
+    arg => {
+      const url = new URL(arg)
+      return url.hostname + url.pathname
+    },
+    arg => {
+      const out = (
+        tempy.directory() + '/' +
+        sanitize(decodeURIComponent(path.basename(arg))))
+
+      return fetch(arg)
+        .then(response => response.buffer())
+        .then(buffer => writeFile(out, buffer))
+        .then(() => out)
+    }),
+
+  youtubedl: cachify('youtubedl',
+    arg => (arg.match(/watch\?v=(.*)/) || ['', arg])[1],
+    arg => {
+      const outDir = tempy.directory()
+      const outFile = outDir + '/%(id)s-%(uploader)s-%(title)s.%(ext)s'
+
+      const opts = [
+        '--quiet',
+        '--no-warnings',
+        '--extract-audio',
+        '--audio-format', downloaders.extension,
+        '--output', outFile,
+        arg
+      ]
+
+      return promisifyProcess(spawn('youtube-dl', opts))
+        .then(() => readdir(outDir))
+        .then(files => outDir + '/' + files[0])
+    }),
+
+  local: cachify('local',
+    arg => arg,
+    arg => {
+      // Usually we'd just return the given argument in a local
+      // downloader, which is efficient, since there's no need to
+      // copy a file from one place on the hard drive to another.
+      // But reading from a separate drive (e.g. a USB stick or a
+      // CD) can take a lot longer than reading directly from the
+      // computer's own drive, so this downloader copies the file
+      // to a temporary file on the computer's drive.
+      // Ideally, we'd be able to check whether a file is on the
+      // computer's main drive mount or not before going through
+      // the steps to copy, but I'm not sure if there's a way to
+      // do that (and it's even less likely there'd be a cross-
+      // platform way).
+
+      // It's possible the downloader argument starts with the "file://"
+      // protocol string; in that case we'll want to snip it off and URL-
+      // decode the string.
+      arg = removeFileProtocol(arg)
+
+      // TODO: Is it necessary to sanitize here?
+      // Haha, the answer to "should I sanitize" is probably always YES..
+      const base = path.basename(arg, path.extname(arg))
+      const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
+
+      return copyFile(arg, out)
+        .then(() => out)
+    }),
+
+  locallink: cachify('locallink',
+    arg => arg,
+    arg => {
+      // Like the local downloader, but creates a symbolic link to the argument.
+
+      arg = removeFileProtocol(arg)
+      const base = path.basename(arg, path.extname(arg))
+      const out = tempy.directory() + '/' + sanitize(base) + path.extname(arg)
+
+      return symlink(path.resolve(arg), out)
+        .then(() => out)
+    }),
 
   echo: arg => arg,
 
diff --git a/list-cache.js b/list-cache.js
deleted file mode 100644
index 71dbbcc..0000000
--- a/list-cache.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const { Base64 } = require('js-base64')
-const fs = require('fs')
-const util = require('util')
-const downloaders = require('./downloaders')
-const ansi = require('./tui-lib/util/ansi')
-
-const readdir = util.promisify(fs.readdir)
-
-async function main(args) {
-  const dlPath = downloaders.rootCacheDir
-  const type = args[0] || null
-
-  async function list(dir) {
-    console.log(await readdir(dir))
-  }
-
-  if (!type) {
-    console.log('No type specified. Pass one of the following as an argument:', (await readdir(dlPath)).join(', '))
-    return
-  }
-
-  console.log(`${ansi.setAttributes([ansi.A_BRIGHT])}Downloads of type "${type}"${ansi.resetAttributes()}`)
-  for (const entry of await readdir(dlPath + '/' + type)) {
-    const files = await readdir(`${dlPath}/${type}/${entry}`)
-    console.log(`  ${Base64.decode(entry)}: ${ansi.setAttributes([ansi.A_BRIGHT])}${files.join(', ')} ${ansi.resetAttributes()}${ansi.setAttributes([ansi.A_DIM])}(${type}/${entry})${ansi.resetAttributes()}`)
-  }
-}
-
-module.exports = main
-
-if (require.main === module) {
-  main(process.argv.slice(2)).catch(err => console.error(err))
-}
diff --git a/package-lock.json b/package-lock.json
index efb723d..b2f1ec5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
-  "name": "music-ui",
-  "version": "1.0.0",
+  "name": "mtui",
+  "version": "0.0.1",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -32,11 +32,6 @@
         "es6-error": "^3.0.1"
       }
     },
-    "js-base64": {
-      "version": "2.4.5",
-      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz",
-      "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ=="
-    },
     "minimist": {
       "version": "0.0.8",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
diff --git a/package.json b/package.json
index dae7b6b..0c5975a 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,6 @@
     "command-exists": "^1.2.6",
     "expand-home-dir": "0.0.3",
     "fifo-js": "^2.1.0",
-    "js-base64": "^2.4.5",
     "mkdirp": "^0.5.1",
     "node-fetch": "^2.1.2",
     "node-natural-sort": "^0.8.6",
diff --git a/todo.txt b/todo.txt
index 2907a02..0e2f6d9 100644
--- a/todo.txt
+++ b/todo.txt
@@ -92,6 +92,7 @@ TODO: Investigate performance issues with very very long ListScrollForms.
 
 TODO: At some point, putting mtui downloads in ~/.mtui - but preferrably with
       a more humanish structure - would be quite nice..!
+      (Done! Yeeeeeeees!!!!!!!!)
 
 TODO: Press "M" to show a context menu. (Done!)