Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement cache limit validation #789

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ let settings = {
'tinker': true
},

tachyonMeta: 'https://tachyon-ci.particle.io/meta'
tachyonMeta: 'https://tachyon-ci.particle.io/meta',
tachyonCacheLimit: 10
hugomontero marked this conversation as resolved.
Show resolved Hide resolved
};

function envValue(varName, defaultValue) {
Expand Down
63 changes: 62 additions & 1 deletion src/lib/download-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class DownloadManager {
this.ui.write(`Using cached file: ${cachedFile}`);
return cachedFile;
}
await this._validateCacheLimit({ url, currentFileName: outputFileName });
const filePath = await this._downloadFile(url, outputFileName, options);
// Validate checksum after download completes
// Validate checksum after download completes
Expand Down Expand Up @@ -96,6 +97,53 @@ class DownloadManager {
}
}

async _validateCacheLimit({ url, maxCacheSize = settings.tachyonCacheLimit, currentFileName }) {
const fileHeaders = await fetch(url, { method: 'HEAD' });

const contentLength = parseInt(fileHeaders.headers.get('content-length') || '0', 10);
// get the size of the download directory
const downloadDirStats = await this.getDownloadedFilesStats(currentFileName);
const totalSize = (downloadDirStats.totalSize + contentLength) / (1024 * 1024 * 1024); // convert to GB
if (totalSize > maxCacheSize && downloadDirStats.fileStats.length > 0) {
// prompt to delete files
const question = {
type: 'confirm',
name: 'cleanCache',
message: 'Downloaded files exceed the cache limit. Do you want to clean up the cache?',
hugomontero marked this conversation as resolved.
Show resolved Hide resolved
default: true
};
const answer = await this.ui.prompt(question);
hugomontero marked this conversation as resolved.
Show resolved Hide resolved
if (answer.cleanCache) {
await this._freeCacheSpace(downloadDirStats);
} else {
// remove files until the cache limit is satisfied
this.ui.write('Cache cleanup skipped. Remove files manually to free up space.');
}
}
return;
}

async getDownloadedFilesStats(currentFile) {
hugomontero marked this conversation as resolved.
Show resolved Hide resolved
const files = await fs.readdir(this.downloadDir);
// filter out the current file in progress
const filteredFiles = currentFile ? files.filter(file => file !== currentFile && file !== `${currentFile}.progress`) : files;
// sort files by date modified
const sortedFileStats = filteredFiles.sort((a, b) => {
const aStats = fs.statSync(path.join(this.downloadDir, a));
const bStats = fs.statSync(path.join(this.downloadDir, b));
return aStats.mtime - bStats.mtime;
});
const fileStats = await Promise.all(sortedFileStats.map(async (file) => {
const filePath = path.join(this.downloadDir, file);
const stats = await fs.stat(filePath);
return { filePath, size: stats.size };
}));
return {
totalSize: fileStats.reduce((sum, file) => sum + file.size, 0),
fileStats
};
}

async _attemptDownload(url, outputFileName, progressFilePath, finalFilePath, timeout) {
const progressBar = this.ui.createProgressBar();
let downloadedBytes = 0;
Expand All @@ -118,7 +166,6 @@ class DownloadManager {
}
await this._streamToFile(response.body, progressFilePath, progressBar, downloadedBytes, timeout, controller);
fs.renameSync(progressFilePath, finalFilePath);
this.ui.write(`Download completed: ${finalFilePath}`);
return finalFilePath;
} finally {
if (progressBar) {
Expand Down Expand Up @@ -219,6 +266,20 @@ class DownloadManager {
throw error;
}
}

async _freeCacheSpace(downloadDirStats) {
const { fileStats, totalSize } = downloadDirStats;
let deletedSize = 0;
for (const file of fileStats) {
const { filePath, size } = file;
await fs.remove(filePath);
this.ui.write(`Removed file: ${file.filePath}`);
hugomontero marked this conversation as resolved.
Show resolved Hide resolved
deletedSize += size;
if ((totalSize - deletedSize) <= settings.tachyonCacheLimit) {
hugomontero marked this conversation as resolved.
Show resolved Hide resolved
return;
}
}
}
}

module.exports = DownloadManager;
16 changes: 16 additions & 0 deletions src/lib/download-manager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('DownloadManager', () => {

afterEach(async () => {
sinon.restore();
nock.cleanAll();
process.env = originalEnv;
await fs.remove(path.join(PATH_TMP_DIR, '.particle/downloads'));
});
Expand Down Expand Up @@ -90,6 +91,9 @@ describe('DownloadManager', () => {
const fileContent = 'This is a test file.';

// Mock the HTTP response
nock(url)
.head(`/${outputFileName}`)
.reply(200);
nock(url)
.get(`/${outputFileName}`)
.reply(200, fileContent);
Expand All @@ -113,6 +117,9 @@ describe('DownloadManager', () => {
fs.writeFileSync(tempFilePath, initialContent);

// Mock the HTTP response with a range
nock(url)
.head(`/${outputFileName}`)
.reply(200);
nock(url, { reqheaders: { Range: 'bytes=10-' } })
.get(`/${outputFileName}`)
.reply(206, remainingContent);
Expand All @@ -130,6 +137,9 @@ describe('DownloadManager', () => {
let error;

// Mock the HTTP response to simulate a failure
nock(url)
.head(`/${outputFileName}`)
.reply(200);
nock(url)
.get(`/${outputFileName}`)
.reply(500);
Expand All @@ -155,6 +165,9 @@ describe('DownloadManager', () => {
let error;

// Mock the HTTP response
nock(url)
.head(`/${outputFileName}`)
.reply(200);
nock(url)
.get(`/${outputFileName}`)
.reply(200, fileContent);
Expand All @@ -177,6 +190,9 @@ describe('DownloadManager', () => {
const checksum = 'f29bc64a9d3732b4b9035125fdb3285f5b6455778edca72414671e0ca3b2e0de';

// Mock the HTTP response
nock(url)
.head(`/${outputFileName}`)
.reply(200);
nock(url)
.get(`/${outputFileName}`)
.reply(200, fileContent);
Expand Down