mediaService.js 9.3 KB

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