740 lines
19 KiB
JavaScript
740 lines
19 KiB
JavaScript
const { ObjectId } = require('mongodb');
|
|
const bcrypt = require("bcryptjs");
|
|
const ffmpeg = require("fluent-ffmpeg");
|
|
const fs = require("fs");
|
|
|
|
const notifier = require("../notifier");
|
|
|
|
const server = require("../../server");
|
|
const config = server.config;
|
|
|
|
const connector = require("./CONNECTOR");
|
|
var dbo;
|
|
connector.connect().then((ret) => {
|
|
dbo = ret;
|
|
});
|
|
|
|
notifier.on("metadata_excluded", item => {
|
|
insert_artist_if_not_exists(item, artist => {
|
|
insert_album_if_not_exists(item, artist, album => {
|
|
insert_track_if_not_exists(item, album);
|
|
});
|
|
});
|
|
});
|
|
|
|
notifier.on("pathdata_excluded", item => {
|
|
insert_box_if_not_exists(item, box => {
|
|
insert_video_if_not_exists(item, box);
|
|
});
|
|
});
|
|
|
|
notifier.on("check_video_details", item => {
|
|
if (!item.tracks) {
|
|
get_video_tracks(item, tracks => {
|
|
item.tracks = tracks;
|
|
update_video(item);
|
|
});
|
|
}
|
|
if (!item.thumbnail) {
|
|
get_video_thumbnail(item, thumbnail => {
|
|
item.thumbnail = thumbnail;
|
|
update_video(item);
|
|
});
|
|
}
|
|
});
|
|
|
|
/*
|
|
MODULES
|
|
*/
|
|
|
|
const albums = require("./albums");
|
|
exports.albums = albums;
|
|
|
|
const radios = require("./radios");
|
|
exports.radios = radios;
|
|
|
|
const tracks = require("./tracks");
|
|
exports.tracks = tracks;
|
|
|
|
const artists = require("./artists");
|
|
exports.artists = artists;
|
|
|
|
const boxes = require("./boxes");
|
|
exports.boxes = boxes;
|
|
|
|
const progress = require("./progress");
|
|
exports.progress = progress;
|
|
|
|
const videos = require("./videos");
|
|
exports.videos = videos;
|
|
|
|
const users = require("./users");
|
|
exports.users = users;
|
|
|
|
const system = require("./system");
|
|
exports.system = system;
|
|
|
|
const share = require("./share");
|
|
exports.share = share;
|
|
|
|
exports.artist_count = function (callback) {
|
|
return dbo.collection("artists").countDocuments(callback);
|
|
};
|
|
|
|
exports.album_count = function (callback) {
|
|
return dbo.collection("albums").countDocuments(callback);
|
|
};
|
|
|
|
exports.track_count = function (callback) {
|
|
return dbo.collection("tracks").countDocuments(callback);
|
|
};
|
|
|
|
exports.box_count = function (callback) {
|
|
return dbo.collection("boxes").countDocuments(callback);
|
|
};
|
|
|
|
exports.video_count = function (callback) {
|
|
return dbo.collection("videos").countDocuments(callback);
|
|
};
|
|
|
|
exports.users_count = function (callback) {
|
|
return dbo.collection("users").countDocuments(callback);
|
|
};
|
|
|
|
/*
|
|
USER
|
|
*/
|
|
|
|
exports.addUser = function (user, callback) {
|
|
bcrypt.genSalt(10, function (err, salt) {
|
|
bcrypt.hash(user.password, salt, function (err, hash) {
|
|
user.password = hash;
|
|
if (!user.roles) {
|
|
user.roles = ["user"];
|
|
}
|
|
user.name = user.name.toLowerCase();
|
|
user.player = {};
|
|
user.fullname = user.name;
|
|
dbo.collection("users").insertOne(user, err => {
|
|
if (err) throw err;
|
|
dbo.collection("users").findOne({ name: user.name }, (err, result) => {
|
|
if (err) throw err;
|
|
delete result.password;
|
|
callback(result);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
exports.deleteUser = function (id, callback) {
|
|
dbo
|
|
.collection("users")
|
|
.deleteOne({ _id: ObjectId(id) }, (err, result) => {
|
|
if (err) throw err;
|
|
callback(result);
|
|
});
|
|
};
|
|
|
|
exports.updateUserRole = function (user, callback) {
|
|
dbo
|
|
.collection("users")
|
|
.updateOne({ name: user.name }, { $set: { roles: user.roles } }, err => {
|
|
if (err) throw err;
|
|
callback();
|
|
});
|
|
};
|
|
|
|
exports.updateUserAccess = function (name) {
|
|
dbo
|
|
.collection("users")
|
|
.updateOne(
|
|
{ name: name },
|
|
{ $set: { last_access: new Date().toLocaleString() } }
|
|
);
|
|
};
|
|
|
|
exports.updateUserPassword = function (user, newPassowrd) {
|
|
bcrypt.genSalt(10, function (err, salt) {
|
|
bcrypt.hash(newPassowrd, salt, function (err, hash) {
|
|
dbo
|
|
.collection("users")
|
|
.updateOne({ name: user }, { $set: { password: hash } },
|
|
err => { if (err) throw err; });
|
|
});
|
|
});
|
|
};
|
|
|
|
exports.updateUserConfig = function (user, body, callback) {
|
|
dbo.collection("users").updateOne(
|
|
{ name: user.name },
|
|
{
|
|
$set: {
|
|
mobile_bpm: body.mobile_bpm,
|
|
desktop_bpm: body.desktop_bpm,
|
|
video_lang: body.video_lang,
|
|
video_quality: body.video_quality,
|
|
fullname: body.fullname
|
|
}
|
|
},
|
|
err => {
|
|
if (err) throw err;
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
exports.updateUserSettings = function (user, callback) {
|
|
dbo
|
|
.collection("users")
|
|
.updateOne(
|
|
{ _id: user._id },
|
|
{ $set: { player: user.player } },
|
|
err => {
|
|
if (err) throw err;
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
exports.userByName = function (name, callback) {
|
|
dbo.collection("users").findOne({ name: name.toLowerCase() }, (err, result) => {
|
|
if (err) throw err;
|
|
callback(result);
|
|
});
|
|
};
|
|
|
|
exports.userById = function (id, callback) {
|
|
dbo
|
|
.collection("users")
|
|
.findOne({ _id: ObjectId(id) }, (err, result) => {
|
|
if (err) throw err;
|
|
callback(result);
|
|
});
|
|
};
|
|
|
|
/*
|
|
HISTORY
|
|
*/
|
|
exports.historyList = function (id, callback) {
|
|
dbo
|
|
.collection("history")
|
|
.find(
|
|
{ userId: id, type: { $in: ['album', 'artist', 'box', 'radio'] } },
|
|
{ projection: { _id: false, userId: false } }
|
|
)
|
|
.sort({ _id: -1 })
|
|
.limit(12)
|
|
.toArray((err, result) => {
|
|
if (err) throw err;
|
|
callback(result);
|
|
});
|
|
};
|
|
|
|
exports.clearHistory = function (id, callback) {
|
|
dbo
|
|
.collection("history")
|
|
.deleteMany({ userId: id }, (err, result) => {
|
|
if (err) throw err;
|
|
callback(result);
|
|
});
|
|
};
|
|
|
|
exports.updateHistory = function (item, callback) {
|
|
dbo
|
|
.collection("history")
|
|
.deleteMany({ userId: item.userId, id: item.id, type: { $in: ['album', 'artist', 'box', 'radio'] } }, err => {
|
|
if (err) throw err;
|
|
dbo
|
|
.collection("history")
|
|
.insertOne(item, err => {
|
|
if (err) throw err;
|
|
callback();
|
|
}
|
|
);
|
|
});
|
|
};
|
|
|
|
exports.search = function (term, callback) {
|
|
let r = [];
|
|
dbo
|
|
.collection("artists")
|
|
.find(
|
|
{ name: { $regex: term, $options: "i" } },
|
|
{ projection: { _id: true, name: true, "covers.cover256": true } }
|
|
)
|
|
.toArray((err, result) => {
|
|
if (result && result.length > 0) {
|
|
result.forEach(item => {
|
|
item.type = "artist";
|
|
item.search_rank = item.name.toLowerCase().indexOf(term);
|
|
});
|
|
r = r.concat(result);
|
|
}
|
|
dbo
|
|
.collection("albums")
|
|
.find(
|
|
{ title: { $regex: term, $options: "i" } },
|
|
{ projection: { _id: true, title: true, "covers.cover128": true } }
|
|
)
|
|
.toArray((err, result) => {
|
|
|
|
if (result && result.length > 0) {
|
|
result.forEach(item => {
|
|
item.type = "album";
|
|
item.parent = {};
|
|
item.search_rank = item.title.toLowerCase().indexOf(term);
|
|
});
|
|
r = r.concat(result);
|
|
}
|
|
dbo
|
|
.collection("tracks")
|
|
.aggregate([
|
|
{
|
|
$lookup: {
|
|
from: "albums",
|
|
localField: "album_id",
|
|
foreignField: "_id",
|
|
as: "parent"
|
|
}
|
|
}, {
|
|
$unwind: "$parent"
|
|
}, {
|
|
$project: {
|
|
album_id: false,
|
|
bitrate: false,
|
|
disk: false,
|
|
duration: false,
|
|
mime: false,
|
|
path: false,
|
|
genre: false,
|
|
track: false,
|
|
"parent.artist_id": false,
|
|
"parent.year": false,
|
|
"parent.covers.cover32": false,
|
|
"parent.covers.cover128": false,
|
|
"parent.covers.cover256": false,
|
|
"parent.covers.cover512": false
|
|
}
|
|
}, {
|
|
$match: { title: { $regex: term, $options: "i" } }
|
|
}])
|
|
.toArray((err, result) => {
|
|
if (result && result.length > 0) {
|
|
result.forEach(item => {
|
|
item.type = "track";
|
|
item.search_rank = item.title.toLowerCase().indexOf(term);
|
|
});
|
|
r = r.concat(result);
|
|
}
|
|
dbo
|
|
.collection("boxes")
|
|
.find(
|
|
{ title: { $regex: term, $options: "i" } },
|
|
{ projection: { _id: true, title: true, "covers.cover128": true } }
|
|
)
|
|
.toArray((err, result) => {
|
|
if (result && result.length > 0) {
|
|
result.forEach(item => {
|
|
item.type = "box";
|
|
item.search_rank = item.title.toLowerCase().indexOf(term);
|
|
});
|
|
r = r.concat(result);
|
|
}
|
|
|
|
dbo
|
|
.collection("videos")
|
|
.aggregate([
|
|
{
|
|
$lookup: {
|
|
from: "boxes",
|
|
localField: "box_id",
|
|
foreignField: "_id",
|
|
as: "parent"
|
|
}
|
|
}, {
|
|
$unwind: "$parent"
|
|
}, {
|
|
$project: {
|
|
path: false,
|
|
mime: false,
|
|
tracks: false,
|
|
box_id: false,
|
|
"parent.box_id": false,
|
|
"parent.year": false,
|
|
"parent.path": false,
|
|
"parent.title": false,
|
|
"parent.covers.cover32": false,
|
|
"parent.covers.cover64": false,
|
|
"parent.covers.cover128": false,
|
|
"parent.covers.cover256": false,
|
|
"parent.covers.cover512": false
|
|
}
|
|
}, {
|
|
$match: { title: { $regex: term, $options: "i" } }
|
|
}]
|
|
)
|
|
.toArray((err, result) => {
|
|
if (result && result.length > 0) {
|
|
result.forEach(item => {
|
|
item.type = "video";
|
|
item.search_rank = item.title
|
|
.toLowerCase()
|
|
.indexOf(term);
|
|
});
|
|
r = r.concat(result);
|
|
}
|
|
dbo
|
|
.collection("radios")
|
|
.find(
|
|
{ name: { $regex: term, $options: "i" } },
|
|
{ projection: { _id: true, name: true } }
|
|
)
|
|
.toArray((err, result) => {
|
|
if (result && result.length > 0) {
|
|
result.forEach(item => {
|
|
item.type = "radio";
|
|
item.search_rank = item.name
|
|
.toLowerCase()
|
|
.indexOf(term);
|
|
});
|
|
r = r.concat(result);
|
|
}
|
|
callback(
|
|
r
|
|
.sort((a, b) => {
|
|
return a.search_rank - b.search_rank;
|
|
})
|
|
.splice(0, 50)
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
/*
|
|
CHAT
|
|
*/
|
|
exports.messageList = function (callback) {
|
|
dbo
|
|
.collection("chat")
|
|
.aggregate([
|
|
{
|
|
$lookup: {
|
|
from: "users",
|
|
localField: "user_id",
|
|
foreignField: "_id",
|
|
as: "user"
|
|
}
|
|
},
|
|
{
|
|
$project: {
|
|
"user.password": false,
|
|
"user.player": false
|
|
}
|
|
},
|
|
{ $unwind: "$user" },
|
|
{ $sort: { _id: 1 } }
|
|
])
|
|
.toArray((err, result) => {
|
|
if (err) throw err;
|
|
callback(result.splice(0, 100));
|
|
});
|
|
};
|
|
|
|
exports.writeMessage = function (user, message, callback) {
|
|
dbo
|
|
.collection("chat")
|
|
.insertOne(
|
|
{ user_id: user._id, message: message, timestamp: Date.now() },
|
|
(err, result) => {
|
|
if (err) throw err;
|
|
callback(result.ops);
|
|
}
|
|
);
|
|
};
|
|
|
|
exports.deleteMessage = function (id, user, callback) {
|
|
dbo.collection("chat").deleteOne({ _id: ObjectId(id), user_id: user._id }, err => {
|
|
if (err) throw err;
|
|
callback();
|
|
});
|
|
};
|
|
|
|
/*
|
|
FUNCTIONS
|
|
*/
|
|
function insert_artist_if_not_exists(item, callback) {
|
|
dbo.collection("artists").findOne({ name: item.artist }, (err, result) => {
|
|
if (err) throw err;
|
|
if (result) {
|
|
callback(result);
|
|
} else {
|
|
let artist = {
|
|
name: item.artist,
|
|
insert_on: Date.now()
|
|
}
|
|
if (item.owner) {
|
|
artist.owner_id = item.owner._id;
|
|
}
|
|
dbo
|
|
.collection("artists")
|
|
.updateOne(
|
|
{ name: item.artist },
|
|
{ $set: artist },
|
|
{ upsert: true },
|
|
err => {
|
|
if (err) throw err;
|
|
insert_artist_if_not_exists(item, callback);
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
function insert_album_if_not_exists(item, artist, callback) {
|
|
dbo
|
|
.collection("albums")
|
|
.findOne({ artist_id: artist._id, title: item.album }, (err, result) => {
|
|
if (err) throw err;
|
|
if (result) {
|
|
callback(result);
|
|
} else {
|
|
let album = {
|
|
artist_id: artist._id,
|
|
artist_name: artist.name,
|
|
title: item.album.trim(),
|
|
year: item.year,
|
|
insert_on: Date.now()
|
|
}
|
|
if (item.covers) {
|
|
album.cover32 = item.covers.cover32;
|
|
album.cover64 = item.covers.cover64;
|
|
album.cover128 = item.covers.cover128;
|
|
album.cover256 = item.covers.cover256;
|
|
}
|
|
if (item.owner) {
|
|
album.owner_id = item.owner._id;
|
|
}
|
|
dbo.collection("albums").updateOne(
|
|
{ artist_id: artist._id, title: item.album },
|
|
{ $set: album },
|
|
{ upsert: true },
|
|
err => {
|
|
if (err) throw err;
|
|
insert_album_if_not_exists(item, artist, callback);
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
function insert_track_if_not_exists(item, album, callback) {
|
|
let short_path = ""
|
|
if (item.path.indexOf(config.upload_folder) == 0) {
|
|
short_path = item.path.replace(config.upload_folder, "");
|
|
} else {
|
|
short_path = item.path.replace(config.music_folder, "");
|
|
}
|
|
|
|
dbo.collection("tracks").updateOne(
|
|
{ path: short_path },
|
|
{
|
|
$set: {
|
|
title: item.title,
|
|
path: short_path,
|
|
album_id: album._id,
|
|
duration: item.duration,
|
|
bitrate: item.bitrate,
|
|
mime: item.mime,
|
|
disk: item.disk,
|
|
track: item.track,
|
|
genre: item.genre,
|
|
insert_on: Date.now()
|
|
}
|
|
},
|
|
{ upsert: true },
|
|
err => {
|
|
if (err) throw err;
|
|
dbo.collection("tracks").findOne({ path: short_path }, (err, result) => {
|
|
if (err) throw err;
|
|
if (callback) {
|
|
callback(result);
|
|
}
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
function insert_box_if_not_exists(item, callback) {
|
|
let short_path = item.box_path.replace(config.video_folder, "");
|
|
dbo
|
|
.collection("boxes")
|
|
.findOne({ title: item.box, year: item.year }, (err, result) => {
|
|
if (err) throw err;
|
|
if (result) {
|
|
callback(result);
|
|
} else {
|
|
let box = {
|
|
title: item.box,
|
|
path: short_path,
|
|
year: item.year,
|
|
cover64: item.cover64,
|
|
cover128: item.cover128,
|
|
cover256: item.cover256,
|
|
cover512: item.cover512,
|
|
insert_on: Date.now()
|
|
}
|
|
if (item.owner) {
|
|
box.owner_id = item.owner._id;
|
|
}
|
|
dbo
|
|
.collection("boxes")
|
|
.updateOne(
|
|
{ title: item.box },
|
|
{ $set: box },
|
|
{ upsert: true },
|
|
err => {
|
|
if (err) throw err;
|
|
insert_box_if_not_exists(item, callback);
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
function insert_video_if_not_exists(item, box, callback) {
|
|
item.path = item.path.replace(config.video_folder, "");
|
|
dbo.collection("videos").updateOne(
|
|
{ path: item.path },
|
|
{
|
|
$set: {
|
|
title: item.title,
|
|
path: item.path,
|
|
box_id: box._id,
|
|
mime: item.mime,
|
|
insert_on: Date.now()
|
|
}
|
|
},
|
|
{ upsert: true },
|
|
err => {
|
|
if (err) throw err;
|
|
dbo.collection("videos").findOne({ path: item.path }, (err, result) => {
|
|
if (err) throw err;
|
|
if (callback) {
|
|
callback(result);
|
|
}
|
|
if (!result.tracks) {
|
|
get_video_tracks(item, tracks => {
|
|
result.tracks = tracks;
|
|
update_video(result);
|
|
});
|
|
}
|
|
if (!result.thumbnail) {
|
|
get_video_thumbnail(result, thumbnail => {
|
|
result.thumbnail = thumbnail;
|
|
update_video(result);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
function update_video(video, callback) {
|
|
process.stdout.write("updating video: " + video.title + "\n");
|
|
let val = {};
|
|
if (video.tracks) {
|
|
val.tracks = video.tracks;
|
|
}
|
|
if (video.thumbnail) {
|
|
val.thumbnail = video.thumbnail;
|
|
}
|
|
|
|
if (!val) {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
return;
|
|
}
|
|
|
|
dbo
|
|
.collection("videos")
|
|
.updateOne({ _id: video._id }, { $set: val }, { upsert: false }, err => {
|
|
if (err) throw err;
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
});
|
|
}
|
|
|
|
function get_video_thumbnail(video, callback) {
|
|
let path = config.video_folder + video.path;
|
|
if (!fs.existsSync(path)) {
|
|
path = config.upload_folder + video.path;
|
|
}
|
|
ffmpeg(path)
|
|
.on("end", () => {
|
|
process.stdout.write("Thumbnail for '" + video.title + "' created\n");
|
|
let thumbnail =
|
|
config.cache_folder + "/video_covers/" + video._id.toString() + ".png";
|
|
if (!fs.existsSync(thumbnail)) {
|
|
callback(null);
|
|
return;
|
|
}
|
|
let bitmap = fs.readFileSync(thumbnail);
|
|
callback("data:image/png;base64, " + bitmap.toString("base64"));
|
|
fs.unlinkSync(thumbnail);
|
|
})
|
|
.on("error", err => {
|
|
process.stdout.write(
|
|
"Thumbnail error for " + video.title + ": " + err.message + "\n"
|
|
);
|
|
callback(null);
|
|
})
|
|
.screenshot({
|
|
timestamps: ["20%"],
|
|
filename: video._id.toString(),
|
|
folder: config.cache_folder + "/video_covers",
|
|
size: "346x180"
|
|
});
|
|
}
|
|
|
|
function get_video_tracks(video, callback) {
|
|
let path = config.video_folder + video.path;
|
|
if (!fs.existsSync(path)) {
|
|
path = config.upload_folder + video.path;
|
|
}
|
|
ffmpeg(path).ffprobe((err, data) => {
|
|
if (!data) {
|
|
return callback([]);
|
|
}
|
|
let audio_streams = data.streams.filter(f => f.codec_type == "audio");
|
|
let return_value = audio_streams.map(m => {
|
|
return {
|
|
index: m.index,
|
|
lang: (m.tags && m.tags.language
|
|
? m.tags.language
|
|
: m.index
|
|
? m.index
|
|
: ""
|
|
).toString(),
|
|
title: (m.tags && m.tags.title
|
|
? m.tags.title
|
|
: m.index
|
|
? m.index
|
|
: ""
|
|
).toString()
|
|
};
|
|
});
|
|
callback(return_value);
|
|
});
|
|
}
|