mediaService.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. const path = require('path');
  2. const fs = require('fs');
  3. const MediaFileModel = require('./mediaItem.js').MediaFileModel;
  4. const { AccessModel, ACCESS_TYPE, ACCESS_TO, ACCESS_GRANT } = require('./access.js');
  5. function Media()
  6. {
  7. }
  8. function MediaStruct(i) {
  9. this.path = i.path;
  10. this.fileName = path.parse(i.path).name;
  11. this.md5sum = i.md5sum;
  12. this.fixedSum = i.fixedSum;
  13. this.date = i.date;
  14. this.meta = {};
  15. this.tags = [];
  16. this.fixedTags = [];
  17. this.accessType = -1;
  18. this.version = i.version;
  19. }
  20. MediaStruct.prototype.pushMeta = function(key, value) {
  21. if (key && value && !this.meta[key]) {
  22. let type = '';
  23. let roTypes = [
  24. 'photochamberImport', 'height', 'width', 'iso', 'focal',
  25. 'fNumber', 'exposureTime', 'camera', 'lensModel',
  26. 'exposureTimeStr', 'libraryPath', 'compression',
  27. 'software', 'fileSize', 'geoHash', 'exposureProgram',
  28. 'orientation'
  29. ];
  30. if (['photochamberImport', 'dateTime'].indexOf(key) >= 0)
  31. type = 'date';
  32. else if (['height', 'width', 'iso', 'focal', 'fNumber', 'exposureTime', 'orientation'].indexOf(key) >= 0)
  33. type = 'number';
  34. else if (['geoHash', 'gpsLocation'].indexOf(key) >= 0)
  35. type = 'geoData';
  36. else if (['artist', 'camera', 'lensModel', 'exposureTimeStr', 'libraryPath', 'compression',
  37. 'software', 'geoCountry', 'geoAdmin', 'geoCity', 'exposureProgram'].indexOf(key) >= 0)
  38. type = 'string';
  39. else if (['fileSize'].indexOf(key) >= 0)
  40. type = 'octet';
  41. else console.log(`Unknown meta type ${key} (${value})`);
  42. this.meta[key] = {
  43. type: type,
  44. canWrite: roTypes.indexOf(key) === -1,
  45. value: value
  46. };
  47. }
  48. }
  49. MediaStruct.prototype.pushTag = function(tag, isFixedTag) {
  50. if (!tag)
  51. return;
  52. if (!isFixedTag && this.tags.indexOf(tag) === -1)
  53. this.tags.push(tag);
  54. if (isFixedTag && this.fixedTags.indexOf(tag) === -1)
  55. this.fixedTags.push(tag);
  56. }
  57. MediaStruct.prototype.computeAccess = function(accessList) {
  58. if (this.accessType > -1)
  59. return this.accessType;
  60. if (!fs.existsSync(this.path))
  61. return this.accessType = ACCESS_GRANT.none;
  62. const checkTag = function(tags, access) {
  63. if (!tags.length)
  64. return false;
  65. for (let i of tags)
  66. if (i.startsWith(access.accessToData+'/') || i === access.accessToData)
  67. return true;
  68. return false;
  69. }
  70. const checkMeta = function(metas, access) {
  71. if (!access.accessToDataDeserialized)
  72. return false;
  73. let metaKey = Object.keys(access.accessToDataDeserialized)[0];
  74. let meta = metas[metaKey]?.value;
  75. return meta && metaKey && meta == access.accessToDataDeserialized[metaKey];
  76. }
  77. this.accessType = ACCESS_GRANT.none;
  78. for (let i of accessList) {
  79. if (i.accessTo === ACCESS_TO.everything ||
  80. (i.accessTo === ACCESS_TO.item && i.accessToData === this.fixedSum) ||
  81. (i.accessTo === ACCESS_TO.meta && checkMeta(this.meta, i)) ||
  82. (i.accessTo === ACCESS_TO.tag && checkTag([].concat(this.fixedTags, this.tags), i))) {
  83. if (i.grant === ACCESS_GRANT.write)
  84. return this.accessType = ACCESS_GRANT.write;
  85. if (i.grant === ACCESS_GRANT.read ||
  86. (i.grant === ACCESS_GRANT.readNoMeta && this.accessType === ACCESS_GRANT.none))
  87. this.accessType = i.grant;
  88. }
  89. }
  90. return this.accessType;
  91. }
  92. MediaStruct.prototype.HaveAccess = function(accessList) {
  93. return this.computeAccess(accessList) > 0;
  94. }
  95. async function buildAccessList(app, accessIds) {
  96. accessIds = Object.keys(accessIds || {}).reduce((acc, i) => {
  97. accessIds[i].linkId && acc.links.push(accessIds[i].linkId);
  98. accessIds[i].ldapDn && acc.ldap.push(accessIds[i].ldapDn);
  99. accessIds[i].email && acc.emails.push(accessIds[i].email);
  100. return acc;
  101. }, {links:[], emails: [], ldap: []});
  102. accessIds.accData = [].concat(accessIds.ldap, accessIds.emails, accessIds.links);
  103. accessIds.links = accessIds.links.map(x => '?').join(',');
  104. accessIds.emails = accessIds.emails.map(x => '?').join(',');
  105. accessIds.ldap = accessIds.ldap.map(x => '?').join(',');
  106. let accessList = (await app.databaseHelper.runSql(`select * from access where (
  107. (type=${ACCESS_TYPE.ldapAccount} AND typeData in (${accessIds.ldap})) OR
  108. (type=${ACCESS_TYPE.email} AND typeData in (${accessIds.emails})) OR
  109. (type=${ACCESS_TYPE.link} AND typeData in (${accessIds.links})) OR
  110. type=${ACCESS_TYPE.everyOne}
  111. )`, accessIds.accData)).map(data => {
  112. let result = new AccessModel;
  113. result.fromDb(data);
  114. return result;
  115. });
  116. return (accessList || []).filter(i => i.grant !== ACCESS_GRANT.none);
  117. }
  118. function reduceReqToMediaStruct(acc, i) {
  119. let obj = acc[i.fixedSum] = acc[i.fixedSum] || new MediaStruct(i);
  120. obj.pushMeta(i.metaKey, i.metaValue);
  121. obj.pushTag(i.mediaTag, i.isFixedTag);
  122. return acc;
  123. }
  124. module.exports.fetchMultiple = async function(app, md5sums, accessList, maxVersion) {
  125. let result = ((await app.databaseHelper.runSql(`
  126. select mediaFile.path, mediaFile.md5sum, mediaFile.date, mediaFile.fixedSum,
  127. mediaMeta.key as metaKey, mediaMeta.value as metaValue,
  128. mediaTag.tag as mediaTag, (mediaTag.fromMeta or mediaTag.fromAutotag) as isFixedTag,
  129. mediaFile.version
  130. from mediaFile
  131. left join mediaMeta on mediaMeta.md5sum=mediaFile.fixedSum
  132. left join mediaTag on mediaTag.md5sum=mediaFile.fixedSum
  133. where mediaFile.inaccessible=0 and mediaFile.version>? and mediaFile.fixedSum in (` +md5sums.map(x => '?').join(',')+`)`, [maxVersion].concat(md5sums))) || []).reduce(reduceReqToMediaStruct, {}) || null;
  134. accessList = await buildAccessList(app, accessList);
  135. for (let key in result)
  136. if (!result[key].HaveAccess(accessList))
  137. delete result[key];
  138. return result;
  139. }
  140. module.exports.fetchOne = async function(app, md5sum, accessList, maxVersion) {
  141. return (await module.exports.fetchMultiple(app, [md5sum], accessList, maxVersion))[md5sum] || null;
  142. }
  143. function fetchAccessSubQuery(args, access) {
  144. let result = [];
  145. if (access.filter(i => i.accessTo === ACCESS_TO.everything).length) return "1=1";
  146. for (let i of access) {
  147. if (i.accessTo === ACCESS_TO.item) {
  148. result.push(`fixedSum=?`);
  149. args.push(i.accessToData);
  150. }
  151. else if (i.accessTo === ACCESS_TO.meta && i.accessToDataDeserialized) {
  152. result.push('(mediaMeta.key=? and mediaMeta.value=?)');
  153. const metaKey = Object.keys(i.accessToDataDeserialized)[0];
  154. args.push(metaKey);
  155. args.push(i.accessToDataDeserialized[metaKey]);
  156. }
  157. else if (i.accessTo === ACCESS_TO.tag && i.accessToData) {
  158. result.push('(mediaTag.tag=? or mediaTag.tag like ?)');
  159. args.push(i.accessToData);
  160. args.push(`${i.accessToData}/%`);
  161. }
  162. }
  163. return result.join(" or ") || "1=0";
  164. }
  165. function fetchMediasBuildSubQuery(startTs, count, access, maxVersion) {
  166. let query = "select distinct fixedSum from mediaFile";
  167. let whereClause = ` where inaccessible=0 and version>?`;
  168. let accessWhere = null;
  169. let args = [maxVersion];
  170. // access filter
  171. if (access !== undefined) {
  172. accessWhere = fetchAccessSubQuery(args, access);
  173. // join
  174. if (accessWhere)
  175. query += ` left join mediaMeta on mediaMeta.md5sum=mediaFile.fixedSum` +
  176. ` left join mediaTag on mediaTag.md5sum=mediaFile.fixedSum`;
  177. if (accessWhere)
  178. whereClause += ` and (${accessWhere})`;
  179. }
  180. // date filter
  181. if (startTs) {
  182. whereClause += ' and date <?'
  183. args.push(startTs);
  184. }
  185. // order and limit
  186. query += whereClause + " order by date desc ";
  187. if (count > 0) {
  188. query += "limit ?";
  189. args.push(count);
  190. }
  191. return {
  192. query: query,
  193. args: args
  194. };
  195. }
  196. module.exports.fetchMedias = async function(app, startTs, count, access, maxVersion) {
  197. let subQuery = fetchMediasBuildSubQuery(startTs, count, access, maxVersion);
  198. let result = ((await app.databaseHelper.runSql(`
  199. select mediaFile.path, mediaFile.md5sum, mediaFile.date, mediaFile.fixedSum,
  200. mediaMeta.key as metaKey, mediaMeta.value as metaValue,
  201. mediaTag.tag as mediaTag, (mediaTag.fromMeta or mediaTag.fromAutotag) as isFixedTag,
  202. mediaFile.version
  203. from mediaFile
  204. left join mediaMeta on mediaMeta.md5sum=mediaFile.fixedSum
  205. left join mediaTag on mediaTag.md5sum=mediaFile.fixedSum
  206. where mediaFile.fixedSum in (${subQuery.query})`, subQuery.args)) || [])
  207. .reduce(reduceReqToMediaStruct, {});
  208. result = Object.keys(result).map(i => result[i]).sort((a, b) => b.date-a.date);
  209. return result;
  210. };
  211. module.exports.fetchMediasSumWithAccess = async function(app, access) {
  212. const subQuery = fetchMediasBuildSubQuery(0, -1, await buildAccessList(app, access), 0);
  213. return (await app.databaseHelper.runSql(subQuery.query, subQuery.args)).map(x => x.fixedSum);
  214. };
  215. module.exports.fetchMediasWithAccess = async function(app, startTs, count, access, maxVersion) {
  216. let result = [];
  217. let lastTs = startTs;
  218. access = await buildAccessList(app, access);
  219. while (result.length < count) {
  220. let tmp = await module.exports.fetchMedias(app, lastTs, 25, access, maxVersion);
  221. if (!tmp.length)
  222. return result;
  223. lastTs = tmp[tmp.length-1].date;
  224. tmp = tmp.filter(i => i.HaveAccess(access));
  225. if (tmp.length)
  226. result = result.concat(tmp);
  227. }
  228. return result.slice(0, count);
  229. };
  230. module.exports.updateVersionInDb = function(app, fixedSum) {
  231. return app.databaseHelper.rawUpdate(MediaFileModel.prototype, { fixedSum: fixedSum }, { version: Date.now() });
  232. }
  233. module.exports.getMediaRange = async function(app) {
  234. let result = await app.databaseHelper.runSql("select min(date) as _min, max(date) as _max, max(version) as _version from mediaFile");
  235. return {
  236. min: result?.[0]?._min || 0,
  237. max: result?.[0]?._max || 0,
  238. maxVersion: result?.[0]?._version || 0
  239. };
  240. }