diff --git a/src/cget/Cache.ts b/src/cget/Cache.ts index d437c7f..85e9f5f 100644 --- a/src/cget/Cache.ts +++ b/src/cget/Cache.ts @@ -103,7 +103,7 @@ export class Cache { createCachePath(urlRemote: string) { return(this.getCachePath(urlRemote).then((cachePath: string) => { - return(mkdirp(path.dirname(cachePath)).then(() => cachePath)); + return(mkdirp(path.dirname(cachePath), this.indexName).then(() => cachePath)); })); } diff --git a/src/cget/Task.ts b/src/cget/Task.ts index d143e7a..be2a9ca 100644 --- a/src/cget/Task.ts +++ b/src/cget/Task.ts @@ -3,16 +3,22 @@ import * as Promise from 'bluebird'; +/** Task wraps a promise, delaying it until some resource gets less busy. */ + export class Task { constructor(func?: () => Promise) { this.func = func; } + /** Start the task immediately and call onFinish callback when done. */ + start(onFinish: (err?: NodeJS.ErrnoException) => void) { // These fix atom-typescript syntax highlight: )) return(this.func().finally(onFinish)); } + /** Wrap task result in a new promise so it can be resolved later. */ + delay() { return(new Promise((resolve: (result: ResultType) => void, reject: (err: any) => void) => { this.resolve = resolve; @@ -20,6 +26,8 @@ export class Task { })); } + /** Resolve the result of a delayed task and call onFinish when done. */ + resume(onFinish: (err?: NodeJS.ErrnoException) => void) { // These fix atom-typescript syntax highlight: )) return(this.start(onFinish).then(this.resolve).catch(this.reject)); diff --git a/src/cget/TaskQueue.ts b/src/cget/TaskQueue.ts index 66365ab..88caa0b 100644 --- a/src/cget/TaskQueue.ts +++ b/src/cget/TaskQueue.ts @@ -4,6 +4,9 @@ import {Task} from './Task' export class TaskQueue { + /** Add a new task to the queue. + * It will start when the number of other concurrent tasks is low enough. */ + add(task: Task) { if(this.busyCount < TaskQueue.concurrency) { // Start the task immediately. @@ -11,15 +14,17 @@ export class TaskQueue { ++this.busyCount; return(task.start(() => this.next())); } else { - // Schedule the task and return a promise that will behave exactly - // like what task.start() returns. + // Schedule the task and return a promise resolving + // to the result of task.start(). this.backlog.push(task); return(task.delay()); } } - next() { + /** Start the next task from the backlog. */ + + private next() { var task = this.backlog.shift(); if(task) task.resume(() => this.next()); diff --git a/src/cget/util.ts b/src/cget/util.ts index 7592828..970d1db 100644 --- a/src/cget/util.ts +++ b/src/cget/util.ts @@ -8,6 +8,8 @@ import * as url from 'url'; import * as path from 'path'; import * as Promise from 'bluebird'; +/** Asynchronous versions of fs methods, wrapped by Bluebird. */ + export var fsa = { stat: Promise.promisify(fs.stat), open: Promise.promisify(fs.open), @@ -22,6 +24,8 @@ export var fsa = { var againSymbol = {}; var again = () => againSymbol; +/** Promise while loop. */ + export function repeat(fn: (again: () => {}) => Promise): Promise { return(Promise.try(() => fn(again) @@ -30,6 +34,8 @@ export function repeat(fn: (again: () => {}) => Promise): Promise { )); } +/** Copy all members of src object to dst object. */ + export function extend(dst: {[key: string]: any}, src: {[key: string]: any}) { for(var key of Object.keys(src)) { dst[key] = src[key]; @@ -38,11 +44,17 @@ export function extend(dst: {[key: string]: any}, src: {[key: string]: any}) { return(dst); } +/** Make shallow clone of object. */ + export function clone(src: Object) { return(extend({}, src)); } -export function mkdirp(pathName: string) { +/** Create a new directory and its parent directories. + * If a path component to create conflicts with an existing file, + * rename to file to /. */ + +export function mkdirp(pathName: string, indexName: string) { var partList = path.resolve(pathName).split(path.sep); var prefixList = partList.slice(0); var pathPrefix: string; @@ -59,14 +71,14 @@ export function mkdirp(pathName: string) { // Trying to convert a file into a directory. // Rename the file to indexName and move it into the new directory. - var tempPath = pathPrefix + '.' + this.makeTempSuffix(6); + var tempPath = pathPrefix + '.' + makeTempSuffix(6); return(Promise.try(() => fsa.rename(pathPrefix, tempPath) ).then(() => fsa.mkdir(pathPrefix) ).then(() => - fsa.rename(tempPath, path.join(pathPrefix, this.indexName)) + fsa.rename(tempPath, path.join(pathPrefix, indexName)) )); } else if(!stats.isDirectory()) { throw(new Error('Tried to create a directory inside something weird: ' + pathPrefix)); @@ -99,7 +111,7 @@ export function mkdirp(pathName: string) { )); } -// Create a string of random letters and numbers. +/** Create a string of random letters and numbers. */ export function makeTempSuffix(length: number) { return(