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

Enabling Download of Content 🗃️ #10

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ coverage
reports
node_modules
*.code-workspace
/tmp
3 changes: 3 additions & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"esversion": 6
}
36 changes: 18 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file removed spec/models/zlibrary-spec.js
Empty file.
30 changes: 29 additions & 1 deletion src/archive-of-anna.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Helpers
const { DOWNLOAD_PATH } = require('./constants');
const downloadHelper = require('./helpers/download-helper');
const searchHelper = require('./helpers/search-helper');

// Libraries
Expand Down Expand Up @@ -37,11 +39,37 @@ class ArchiveOfAnna {
* @return {Object} The content of the response.
* TODO: Fix documentation.
*/
static async fetch_by_md5(md5) {
static async fetchByMd5(md5) {
const url = searchHelper.buildFetchUrl(md5);
const response = await axiosHelper.get(url);
return searchHelper.getContent(response.data);
}

/**
* It takes an array of IPFS links and returns a promise that resolves to an array of the same length,
* where each element is the file contents of the corresponding IPFS link
* @param {Array} ipfsLinks - An array of IPFS links.
* @param {String|undefined} name - Name for the file to be saved as.
* @param {String} path - Path to save the file to.
* @return {File} the result of the downloadHelper.ipfs function.
* TODO: Fix Documentation.
*/
static downloadFileViaIpfs(ipfsLinks, name = undefined, path = DOWNLOAD_PATH) {
return downloadHelper.ipfs(ipfsLinks, name, path);
}

/**
* It takes a list of links to Libgen, and downloads the files
* @param {Array} libgenLinks - An array of links to the libgen mirrors.
* @param {String|undefined} fork - The fork to download the file from.
* @param {String|undefined} name - Name for the file to be saved as.
* @param {String} path - Path to save the file to.
* @return {File} A promise.
* TODO: Fix Documentation
*/
static downloadFileViaLibgen(libgenLinks, fork = undefined, name = undefined, path = DOWNLOAD_PATH) {
return downloadHelper.libgenDownload(libgenLinks, fork, name, path);
}
}

module.exports = ArchiveOfAnna;
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
BASE_URI: 'https://annas-archive.org',
DOWNLOAD_PATH: './tmp/',
PATH_PREFIXES: {
MD5: '/md5/',
},
Expand Down
55 changes: 55 additions & 0 deletions src/helpers/download-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const axiosHelper = require('../libraries/axios-helper');
const fileHelper = require('../libraries/file-helper');

const getSubstringIndicesForFilename = (contentDispositionHeader) => {
const filenameHeader = 'filename="';
const startIndex = contentDispositionHeader.indexOf(filenameHeader) + filenameHeader.length;
const endIndex = contentDispositionHeader.indexOf('"', startIndex);

return [startIndex, endIndex];
};

const getFileNameFromHeaders = (response) => {
const contentDispositionHeader = response.headers['content-disposition'];
const [startIndex, endIndex] = getSubstringIndicesForFilename(contentDispositionHeader);

return contentDispositionHeader.substring(startIndex, endIndex);
};

const getFileExtensionFromHeaders = (response) => {
const fileName = getFileNameFromHeaders(response);
return fileName.substring(fileName.lastIndexOf('.') + 1);
};

const getCustomFileName = (name) => `${name}.${getFileExtensionFromHeaders(response)}`;

const getFileNameAndContent = async (name, link) => {
const response = await axiosHelper.download(link, name);
if (response.status != 200) return [null, null];

const fileName = name ? getCustomFileName(name) : getFileNameFromHeaders(response);

return [fileName, response.data];
};

const downloadFileFromGivenLinks = async (links, name, path) => {
fileHelper.directorySetup(path); // Setup the download directory if it doesn't exist.

for (const link of links) {
const [fileName, content] = await getFileNameAndContent(name, link);

if (!content) continue;

return await fileHelper.writeFileToPath(path, fileName, content);
}

throw new Error('File could not be downloaded due to server error. Please try again later.');
};

const downloadHelper = {
ipfs: async (ipfsLinks, name, path) => await downloadFileFromGivenLinks(ipfsLinks, name, path),
libgenDownload: (libgenLinks, fork, name, path) => {},
torDownload: () => {}, // TODO: Not in current scope. Need to check if axios can be self-contained for Tor requests.
};

module.exports = downloadHelper;
8 changes: 8 additions & 0 deletions src/interface/download-progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const store = require('../store');

module.exports = {
getProgress: (key) => store.downloadProgress[key],
setProgress: (key, progressEvent) => {
store.downloadProgress[key] = Math.floor((progressEvent.loaded * 100) / progressEvent.total);
},
};
17 changes: 16 additions & 1 deletion src/libraries/axios-helper.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
const axios = require('axios');
const { getProgress, setProgress } = require('../interface/download-progress');

const headers = { 'User-Agent': 'PostmanRuntime/7.30.0' };

const axiosHelper = {
get: (url) => axios.get(url, { headers: { 'User-Agent': 'PostmanRuntime/7.30.0' } }),
get: (url) => axios.get(url, { headers: headers }),
download: (url, name = undefined) => {
const downloadProgressKey = name || url;

return axios.get(url, {
headers: headers,
responseType: 'stream',
onDownloadProgress: (progressEvent) => {
setProgress(downloadProgressKey, progressEvent);
console.log('Current Progress: ' + getProgress(downloadProgressKey) + '%'); // TODO: Remove this console log after setting up hooks.
},
});
},
};

module.exports = axiosHelper;
24 changes: 24 additions & 0 deletions src/libraries/file-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const fs = require('fs');
const util = require('util');
const stream = require('stream');
const pipeline = util.promisify(stream.pipeline);
const nodePath = require('path');

// ? Do I need this as a helper method across the library?
const pathJoiner = (...paths) => nodePath.join(...paths);

const fileHelper = {
writeFileToPath: async (path, fileName, content) => {
const writeTo = pathJoiner(path, fileName);
const writer = fs.createWriteStream(writeTo);

await pipeline(content, writer);

return fs.existsSync(writeTo);
},
directorySetup: (path) => {
fs.existsSync(path) || fs.mkdirSync(path, { recursive: true });
},
};

module.exports = fileHelper;
3 changes: 3 additions & 0 deletions src/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
downloadProgress: {},
};