Queue for tag downloads
This commit is contained in:
		
							parent
							
								
									be489e9a18
								
							
						
					
					
						commit
						5e9ea6db66
					
				
					 6 changed files with 58 additions and 8 deletions
				
			
		
							
								
								
									
										9
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -7,8 +7,9 @@
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "bc-explorer",
 | 
					      "name": "bc-explorer",
 | 
				
			||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
      "license": "ISC",
 | 
					      "license": "AGPL-3.0-only",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@chriscdn/promise-semaphore": "^2.0.10",
 | 
				
			||||||
        "@cloudrac3r/pug": "^4.0.4",
 | 
					        "@cloudrac3r/pug": "^4.0.4",
 | 
				
			||||||
        "@iconify-json/iconoir": "^1.2.7",
 | 
					        "@iconify-json/iconoir": "^1.2.7",
 | 
				
			||||||
        "@iconify/utils": "^2.3.0",
 | 
					        "@iconify/utils": "^2.3.0",
 | 
				
			||||||
| 
						 | 
					@ -85,6 +86,12 @@
 | 
				
			||||||
        "node": ">=6.9.0"
 | 
					        "node": ">=6.9.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@chriscdn/promise-semaphore": {
 | 
				
			||||||
 | 
					      "version": "2.0.10",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@chriscdn/promise-semaphore/-/promise-semaphore-2.0.10.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-NagoHAZEYISDYYprsHe+x2BEcD6GKhTpEreI8BM1qgtHOtCS3lbwRvvTQxzAxU8JVSmw7ep/ROLv3Ng/MPcMHg==",
 | 
				
			||||||
 | 
					      "license": "MIT"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@cloudrac3r/pug": {
 | 
					    "node_modules/@cloudrac3r/pug": {
 | 
				
			||||||
      "version": "4.0.4",
 | 
					      "version": "4.0.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@cloudrac3r/pug/-/pug-4.0.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@cloudrac3r/pug/-/pug-4.0.4.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,8 +8,9 @@
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "keywords": [],
 | 
					  "keywords": [],
 | 
				
			||||||
  "author": "",
 | 
					  "author": "",
 | 
				
			||||||
  "license": "ISC",
 | 
					  "license": "AGPL-3.0-only",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@chriscdn/promise-semaphore": "^2.0.10",
 | 
				
			||||||
    "@cloudrac3r/pug": "^4.0.4",
 | 
					    "@cloudrac3r/pug": "^4.0.4",
 | 
				
			||||||
    "@iconify-json/iconoir": "^1.2.7",
 | 
					    "@iconify-json/iconoir": "^1.2.7",
 | 
				
			||||||
    "@iconify/utils": "^2.3.0",
 | 
					    "@iconify/utils": "^2.3.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								pug/home.pug
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								pug/home.pug
									
										
									
									
									
								
							| 
						 | 
					@ -6,6 +6,7 @@ html
 | 
				
			||||||
    title BC Explorer
 | 
					    title BC Explorer
 | 
				
			||||||
    link(rel="icon" href="/favicon.png")
 | 
					    link(rel="icon" href="/favicon.png")
 | 
				
			||||||
    link(rel="stylesheet" type="text/css" href="/static/stacks.css")
 | 
					    link(rel="stylesheet" type="text/css" href="/static/stacks.css")
 | 
				
			||||||
 | 
					    link(rel="stylesheet" type="text/css" href="/static/style.css")
 | 
				
			||||||
    script(src="/static/htmx.js")
 | 
					    script(src="/static/htmx.js")
 | 
				
			||||||
    meta(name="htmx-config" content='{"requestClass":"is-loading"}')
 | 
					    meta(name="htmx-config" content='{"requestClass":"is-loading"}')
 | 
				
			||||||
  body.themed.theme-system.overflow-y-scroll
 | 
					  body.themed.theme-system.overflow-y-scroll
 | 
				
			||||||
| 
						 | 
					@ -30,3 +31,17 @@ html
 | 
				
			||||||
          input.s-input.wmx3#username(name="account" placeholder="Enter your Bandcamp username here")
 | 
					          input.s-input.wmx3#username(name="account" placeholder="Enter your Bandcamp username here")
 | 
				
			||||||
        button.s-btn.s-btn__filled.my16#submit-username Load collection
 | 
					        button.s-btn.s-btn__filled.my16#submit-username Load collection
 | 
				
			||||||
        #results.d-flex
 | 
					        #results.d-flex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .s-prose.mt32
 | 
				
			||||||
 | 
					        main
 | 
				
			||||||
 | 
					          h2 About BC Explorer
 | 
				
			||||||
 | 
					          p Explore your Bandcamp collection online!
 | 
				
			||||||
 | 
					          p You can easily search your whole collection and play it streaming rather than downloading every mp3 to your computer and using a media player.
 | 
				
			||||||
 | 
					          aside: p.fs-fine (you should download every mp3, though, because the Bandcamp TOS says they can take away your online access at any time)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        footer.mt32.d-flex.fw-wrap.gx8.ai-center
 | 
				
			||||||
 | 
					          a(href="https://cadence.moe") Created by Cadence
 | 
				
			||||||
 | 
					          .s-award-bling.s-award-bling__silver.ml4
 | 
				
			||||||
 | 
					          a(href="https://gitdab.com/cadence/bc-explorer") BC Explorer source code
 | 
				
			||||||
 | 
					          .s-award-bling.s-award-bling__silver.ml4
 | 
				
			||||||
 | 
					          span Not affiliated with Bandcamp.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,15 @@
 | 
				
			||||||
if downloader.total > 0 || downloader.outcome
 | 
					if downloader.total > 0 || downloader.outcome
 | 
				
			||||||
  #tag-download
 | 
					  #tag-download
 | 
				
			||||||
    if !downloader.running && !downloader.outcome
 | 
					    if downloader.queuePosition > 0
 | 
				
			||||||
 | 
					      .s-notice.p16(role="status" hx-target="#tag-download" hx-select="#tag-download" hx-get=`/api/tag-download?account=${account}` hx-trigger="every 30s" hx-indicator="null")
 | 
				
			||||||
 | 
					        .d-flex.gx16.ai-center
 | 
				
			||||||
 | 
					          != icons.get("cloud-download")
 | 
				
			||||||
 | 
					          .fl-grow1 Waiting to download...
 | 
				
			||||||
 | 
					        p.mt12.mb0 #{downloader.queuePosition} people are ahead of you in the queue.
 | 
				
			||||||
 | 
					        p.mt12.mb0 You can keep using BC Explorer and this will process in the background.
 | 
				
			||||||
 | 
					        title#title(hx-swap-oob="true") In queue | Tags | BC Explorer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    else if !downloader.running && !downloader.outcome
 | 
				
			||||||
      form.s-notice.s-notice__info.d-flex.ai-center.p8.gx16.pl16(role="status" hx-target="#tag-download" hx-select="#tag-download" hx-post="/api/tag-download")
 | 
					      form.s-notice.s-notice__info.d-flex.ai-center.p8.gx16.pl16(role="status" hx-target="#tag-download" hx-select="#tag-download" hx-post="/api/tag-download")
 | 
				
			||||||
        != icons.get("info-circle")
 | 
					        != icons.get("info-circle")
 | 
				
			||||||
        div Tag data needs to be downloaded. This will take a while.
 | 
					        div Tag data needs to be downloaded. This will take a while.
 | 
				
			||||||
| 
						 | 
					@ -28,7 +37,7 @@ if downloader.total > 0 || downloader.outcome
 | 
				
			||||||
        != icons.get("cloud-check")
 | 
					        != icons.get("cloud-check")
 | 
				
			||||||
        .fl-grow1 Tags downloaded.
 | 
					        .fl-grow1 Tags downloaded.
 | 
				
			||||||
        - downloader.resolve()
 | 
					        - downloader.resolve()
 | 
				
			||||||
        a.s-btn.s-btn__outlined(href=and({arrange: "tag"}) hx-boost="true") Refresh
 | 
					        a.s-btn.s-btn__outlined(href=and({arrange: "tag", shape: "grid"})) Refresh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      .s-notice.s-notice__danger.p8.gx16.pl16.d-flex.ai-center
 | 
					      .s-notice.s-notice__danger.p8.gx16.pl16.d-flex.ai-center
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,7 +82,7 @@ async function loadCollection(inputUsername) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// load full tracks/tags immediately if there's not too many
 | 
						// load full tracks/tags immediately if there's not too many
 | 
				
			||||||
	const downloader = loadTags.downloadManager.check(account)
 | 
						const downloader = loadTags.downloadManager.check(account)
 | 
				
			||||||
	if (downloader.total <= 5) loadTags.downloadManager.start(account)
 | 
						if (downloader.total > 0 && downloader.total <= 20 && loadTags.downloadManager.queue === 0) loadTags.downloadManager.start(account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		storedItemCount,
 | 
							storedItemCount,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const domino = require("domino")
 | 
					const domino = require("domino")
 | 
				
			||||||
const {getValidatedQuery, readValidatedBody, defineEventHandler} = require("h3")
 | 
					const {getValidatedQuery, readValidatedBody, defineEventHandler} = require("h3")
 | 
				
			||||||
 | 
					/** @type {import("@chriscdn/promise-semaphore")["default"]} */ // @ts-ignore
 | 
				
			||||||
 | 
					const Semaphore = require("@chriscdn/promise-semaphore")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {sync, db, router} = require("../passthrough")
 | 
					const {sync, db, router} = require("../passthrough")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +24,7 @@ class TagDownloader extends sync.reloadClassMethods(() => TagDownloader) {
 | 
				
			||||||
		this.untaggedItems = []
 | 
							this.untaggedItems = []
 | 
				
			||||||
		this.total = this.untaggedItems.length
 | 
							this.total = this.untaggedItems.length
 | 
				
			||||||
		this.running = false
 | 
							this.running = false
 | 
				
			||||||
 | 
							this.queuePosition = 0
 | 
				
			||||||
		this.outcome = null
 | 
							this.outcome = null
 | 
				
			||||||
		this.check()
 | 
							this.check()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -32,8 +35,14 @@ class TagDownloader extends sync.reloadClassMethods(() => TagDownloader) {
 | 
				
			||||||
		this.total = this.untaggedItems.length
 | 
							this.total = this.untaggedItems.length
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_setQueued(queuePosition) {
 | 
				
			||||||
 | 
							if (this.running) return
 | 
				
			||||||
 | 
							this.queuePosition = queuePosition
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async _start() {
 | 
						async _start() {
 | 
				
			||||||
		if (this.running) return
 | 
							if (this.running) return
 | 
				
			||||||
 | 
							this.queuePosition = 0
 | 
				
			||||||
		this.running = true
 | 
							this.running = true
 | 
				
			||||||
		this.outcome = null
 | 
							this.outcome = null
 | 
				
			||||||
		this.processed = 0
 | 
							this.processed = 0
 | 
				
			||||||
| 
						 | 
					@ -79,7 +88,7 @@ class TagDownloader extends sync.reloadClassMethods(() => TagDownloader) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			this.outcome = "Success"
 | 
								this.outcome = "Success"
 | 
				
			||||||
		} catch (e) {
 | 
							} catch (e) {
 | 
				
			||||||
			console.error(e)
 | 
								console.error(`error downloading tags for ${this.account} - ${e}`)
 | 
				
			||||||
			this.outcome = e.toString()
 | 
								this.outcome = e.toString()
 | 
				
			||||||
		} finally {
 | 
							} finally {
 | 
				
			||||||
			this.running = false
 | 
								this.running = false
 | 
				
			||||||
| 
						 | 
					@ -94,6 +103,8 @@ class TagDownloader extends sync.reloadClassMethods(() => TagDownloader) {
 | 
				
			||||||
const downloadManager = new class {
 | 
					const downloadManager = new class {
 | 
				
			||||||
	/** @type {Map<string, TagDownloader>} */
 | 
						/** @type {Map<string, TagDownloader>} */
 | 
				
			||||||
	inProgressTagDownloads = sync.remember(() => new Map())
 | 
						inProgressTagDownloads = sync.remember(() => new Map())
 | 
				
			||||||
 | 
						semaphore = sync.remember(() => new Semaphore(1))
 | 
				
			||||||
 | 
						queue = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/** @param {string} account */
 | 
						/** @param {string} account */
 | 
				
			||||||
	check(account) {
 | 
						check(account) {
 | 
				
			||||||
| 
						 | 
					@ -109,14 +120,21 @@ const downloadManager = new class {
 | 
				
			||||||
	/** @param {string} account */
 | 
						/** @param {string} account */
 | 
				
			||||||
	start(account) {
 | 
						start(account) {
 | 
				
			||||||
		const downloader = this.check(account)
 | 
							const downloader = this.check(account)
 | 
				
			||||||
		downloader._start()
 | 
							downloader._setQueued(++this.queue)
 | 
				
			||||||
 | 
							console.log(`requested tag download for ${account} - ${this.queue} in queue`)
 | 
				
			||||||
 | 
							this.semaphore.request(() => downloader._start()).finally(() => {
 | 
				
			||||||
 | 
								this.queue--
 | 
				
			||||||
 | 
								for (const otherDownloader of this.inProgressTagDownloads.values()) {
 | 
				
			||||||
 | 
									otherDownloader.queuePosition--
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
		return downloader
 | 
							return downloader
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/** @param {string} account */
 | 
						/** @param {string} account */
 | 
				
			||||||
	resolve(account) {
 | 
						resolve(account) {
 | 
				
			||||||
		const downloader = this.check(account)
 | 
							const downloader = this.check(account)
 | 
				
			||||||
		if (!downloader.running) {
 | 
							if (!downloader.running && downloader.queuePosition <= 0) {
 | 
				
			||||||
			this.inProgressTagDownloads.delete(account)
 | 
								this.inProgressTagDownloads.delete(account)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue