mediamonkeyserver
Version:
MediaMonkey Server
381 lines (289 loc) • 9.81 kB
JavaScript
/*jslint node: true, nomen: true, esversion: 6, maxlen: 180 */
"use strict";
const assert = require('assert');
const Mime = require('mime');
const fs = require('fs');
const Path = require('path');
const request = require('request');
const crypto = require('crypto');
const Async = require('async');
const Url = require('url');
const https = require('https');
const debug = require('debug')('upnpserver:contentProviders:1Fichier');
const logger = require('../logger');
const URL = require('../util/url');
const ContentProvider = require('./contentProvider');
const DIRECTORY_MIME_TYPE = "inode/directory";
const USER_AGENT = 'Upnpserver/1.0';
var streamID=0;
class OneFichierContentProvider extends ContentProvider {
constructor(configuration) {
super(configuration);
this._cache={};
this._userAgent = USER_AGENT;
this._requestQueue = Async.queue((task, callback) => task(callback), configuration.maxRequest || 2);
if (!configuration.username) {
throw new Error("Username must be specified !");
}
if (!configuration.password) {
throw new Error("Password must be specified !");
}
this._baseURL=configuration.baseURL || "https://1fichier.com/";
this._username=this.normalizeParameter(configuration.username);
var password=this.normalizeParameter(configuration.password);
this._password=password;
this._passwordMD5=crypto.createHash('md5').update(password).digest("hex");
if (!this._username || !this._password) {
throw new Error("Username or password must be defined !");
}
debug("Set baseURL to", this._baseURL, "username=", this._username, "password=", this._passwordMD5);
}
initialize(service, callback) {
super.initialize(service, (error) => {
if (error) {
return callback(error);
}
this._userAgent=[ "Node/" + process.versions.node, "UPnP/1.0",
"UPnPServer/" + service.upnpServer.packageDescription.version ].join(' ');
callback();
});
}
/**
*
*/
_convertURL(contentURL) {
var url=contentURL.substring(this.protocol.length+1);
if (!url || url==='/') {
url=this._baseURL+"console/get_folder_content.pl";
} else {
var reg=/([^/]+)\/([^\/]+)$/.exec(url);
if (reg) {
url = this._baseURL+"console/get_folder_content.pl?id="+reg[2];
} else {
reg=/\?([^?]+)$/.exec(url);
url = this._baseURL+"?"+reg[1];
}
}
debug("Convert content URL of",contentURL,"=>",url);
return url;
}
/**
*
*/
readdir(contentURL, callback) {
var url=this._convertURL(contentURL);
var folderId="0";
var reg=/\/([^\/]+)$/.exec(contentURL);
if (reg) {
folderId=reg[1];
}
debug("Readdir",contentURL,"folderId=",folderId);
this._requestQueue.push((callback) => this._readdir(url, folderId, callback), callback);
}
_readdir(url, folderId, callback) {
if (this._badPassword) {
return callback(new Error("Bad password detected !"));
}
var options = {
qs: {
user: this._username,
pass: this._passwordMD5
}
};
request(url, options, (error, response, body) => {
// debug("Readdir Body=",body);
if (error) {
logger.error("Can not read directory ",url,error);
error.url=url;
return callback(error);
}
if (response.statusCode===403) {
this._badPassword=true;
return callback(new Error("Bad password detected !"));
}
var json;
try {
json = JSON.parse(body);
} catch (x) {
x.url=url;
x.body=body;
return callback(x);
}
debug("_readDir", "json=", json);
if (!(json instanceof Array)) {
var err=new Error("Invalid readdir response for url="+folderId);
err.body=body;
return callback(err);
}
var ret=json.map((f) => {
var stat=this._createStat(f, folderId);
this._cache[stat.url]=stat;
debug("_readDir", "stat=", stat);
return this.newURL(stat.url);
});
callback(null, ret);
});
}
_createStat(f, parentId) {
var stat={
name: f.name,
mtime: new Date(f.date),
type: f.type ,
isDirectory: () => f.type==="d",
isFile: () => f.type!=="d"
};
if (f.type==="d") {
var reg=/console\/get_folder_content\.pl\?id=(.+)$/.exec(f.url);
stat.url=this.protocol+":"+parentId+"/"+(reg && reg[1]);
stat.mimeType=DIRECTORY_MIME_TYPE;
} else {
stat.size=parseInt(f.size, 10);
var reg2=/\?(.+)$/.exec(f.url);
stat.url=this.protocol+":"+parentId+"?"+reg2[1];
stat.mimeType=f.mimeType || Mime.getType(f.name);
}
return stat;
}
/**
*
*/
stat(contentURL, callback) {
var stat=this._cache[contentURL];
if (stat) {
debug("stat", "Stat is in cache",stat);
return callback(null, stat);
}
if (true) {
// Stat parent !
var reg=/:([^/]+)\/([^\/]+)$/.exec(contentURL);
if (!reg) {
reg=/:([^?]*)\?(\/.+)$/.exec(contentURL);
}
var url=this._baseURL+"console/get_folder_content.pl?id="+reg[1];
this._readdir(url, reg[1], (error, stats) => {
if (error) {
return callback(error);
}
var stat=stats.find((s) => s.url===contentURL);
callback(null, stat);
});
return;
}
this._requestQueue.push((callback) => this._stat(contentURL, callback), callback);
}
_stat(contentURL, callback) {
if (this._badPassword) {
return callback(new Error("Bad password detected !"));
}
var url=this._convertURL(contentURL);
debug("_stat", "contentURL=", contentURL);
var options = {
method: "HEAD",
followRedirect: false,
qs: {
user: this._username,
pass: this._passwordMD5
}
};
debug("_stat", "Http request", options);
request(url, options, (error, response, body) => {
debug("_stat", "Body=",body);
if (error) {
return callback(error);
}
if (response.statusCode===403) {
this._badPassword=true;
return callback(new Error("Bad password detected !"));
}
var ret=[];
callback(null, ret);
});
}
/**
*
*/
createReadStream(session, contentURL, options, callback) {
debug("createReadStream", "Create url=", contentURL, "options=",options);
var url=this._convertURL(contentURL);
this._requestQueue.push((callback) => this._createReadStream(url, options, callback), (error, stream) => {
if (error) {
logger.error("_createReadStream from url="+url+" throws an error", error);
return callback(error);
}
debug("createReadStream", "returns stream");
callback(null, stream);
});
}
/**
*
*/
_createReadStream(url, options, callback) {
debug("_createReadStream", "url=",url,"options=",options);
if (this._badPassword) {
return callback(new Error("Bad password detected !"));
}
var requestOptions = Url.parse(url);
requestOptions.headers=requestOptions.headers || {};
requestOptions.headers.Authorization="Basic "+new Buffer(this._username + ":" + this._password).toString("base64");
requestOptions.headers['User-Agent']=this._userAgent;
if (options) {
if (options.start) {
var bs="bytes "+options.start;
if (options.end) {
bs+=options.end+"/"+(options.end-options.start+1);
} else {
bs+="*/*";
}
requestOptions.headers['content-range']=bs;
}
}
debug("Request options=", requestOptions);
var req = https.request(requestOptions, (response) => {
var sid=streamID++;
debug("_createReadStream", "Get response url=", url, "statusCode=",response.statusCode,"stream=#",sid);
if (response.statusCode===403) {
this._badPassword=true;
return callback(new Error("Bad password detected !"));
}
if (response.statusCode===302) {
var location=response.headers.location;
debug("_createReadStream", "Redirect to",location);
if (location && location!==url) {
setImmediate(() => {
this._createReadStream(location, options, callback);
});
}
return;
}
if (Math.floor(response.statusCode/100)!=2) {
logger.error("Invalid status code "+response.statusCode+" for url "+url);
var ex=new Error("Invalid status code "+response.statusCode);
return callback(ex);
}
if (debug.enabled) {
var count=0;
response.on('data', (chunk) => {
count+=chunk.length;
debug('Stream #',sid,'(',url,') Load',count,'bytes');
});
response.on('end', (chunk) => {
debug('Stream #',sid,'(',url,') CLOSED');
});
}
callback(null, response);
});
req.on("error", (error) => {
logger.error("_createReadStream: Catch error for url=",url,"error=",error,error.stack);
error.url=url;
callback(error);
});
req.end();
}
/**
*
*/
toString() {
return "[1Fichier ContentProvider name='"+this.name+"' username='"+this._username+"']";
}
}
module.exports = OneFichierContentProvider;