reader.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import axios from 'axios';
  2. import * as utils from '../share/utils';
  3. import * as cryptoUtils from '../share/cryptoUtils';
  4. import wsc from './webSocketConnection';
  5. const api = axios.create({
  6. baseURL: '/api/reader'
  7. });
  8. const workerApi = axios.create({
  9. baseURL: '/api/worker'
  10. });
  11. class Reader {
  12. constructor() {
  13. }
  14. async getWorkerStateFinish(workerId, callback) {
  15. if (!callback) callback = () => {};
  16. let response = {};
  17. try {
  18. const requestId = await wsc.send({action: 'worker-get-state-finish', workerId});
  19. let prevResponse = false;
  20. while (1) {// eslint-disable-line no-constant-condition
  21. response = await wsc.message(requestId);
  22. if (!response.state && prevResponse !== false) {//экономия траффика
  23. callback(prevResponse);
  24. } else {//были изменения worker state
  25. if (!response.state)
  26. throw new Error('Неверный ответ api');
  27. callback(response);
  28. prevResponse = response;
  29. }
  30. if (response.state == 'finish' || response.state == 'error') {
  31. break;
  32. }
  33. }
  34. return response;
  35. } catch (e) {
  36. console.error(e);
  37. }
  38. //если с WebSocket проблема, работаем по http
  39. const refreshPause = 500;
  40. let i = 0;
  41. response = {};
  42. while (1) {// eslint-disable-line no-constant-condition
  43. const prevProgress = response.progress || 0;
  44. const prevState = response.state || 0;
  45. response = await workerApi.post('/get-state', {workerId});
  46. response = response.data;
  47. callback(response);
  48. if (!response.state)
  49. throw new Error('Неверный ответ api');
  50. if (response.state == 'finish' || response.state == 'error') {
  51. break;
  52. }
  53. if (i > 0)
  54. await utils.sleep(refreshPause);
  55. i++;
  56. if (i > 180*1000/refreshPause) {//3 мин ждем телодвижений воркера
  57. throw new Error('Слишком долгое время ожидания');
  58. }
  59. //проверка воркера
  60. i = (prevProgress != response.progress || prevState != response.state ? 1 : i);
  61. }
  62. return response;
  63. }
  64. async loadBook(opts, callback) {
  65. if (!callback) callback = () => {};
  66. let response = await api.post('/load-book', opts);
  67. const workerId = response.data.workerId;
  68. if (!workerId)
  69. throw new Error('Неверный ответ api');
  70. callback({totalSteps: 4});
  71. callback(response.data);
  72. response = await this.getWorkerStateFinish(workerId, callback);
  73. if (response) {
  74. if (response.state == 'finish') {//воркер закончил работу, можно скачивать кешированный на сервере файл
  75. callback({step: 4});
  76. const book = await this.loadCachedBook(response.path, callback, response.size);
  77. return Object.assign({}, response, {data: book.data});
  78. }
  79. if (response.state == 'error') {
  80. let errMes = response.error;
  81. if (errMes.indexOf('getaddrinfo') >= 0 ||
  82. errMes.indexOf('ECONNRESET') >= 0 ||
  83. errMes.indexOf('EINVAL') >= 0 ||
  84. errMes.indexOf('404') >= 0)
  85. errMes = `Ресурс не найден по адресу: ${response.url}`;
  86. throw new Error(errMes);
  87. }
  88. } else {
  89. throw new Error('Пустой ответ сервера');
  90. }
  91. }
  92. async checkCachedBook(url) {
  93. let estSize = -1;
  94. try {
  95. const response = await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
  96. if (response.headers['content-length']) {
  97. estSize = response.headers['content-length'];
  98. }
  99. } catch (e) {
  100. //восстановим при необходимости файл на сервере из удаленного облака
  101. let response = null
  102. try {
  103. response = await wsc.message(await wsc.send({action: 'reader-restore-cached-file', path: url}));
  104. } catch (e) {
  105. console.error(e);
  106. //если с WebSocket проблема, работаем по http
  107. response = await api.post('/restore-cached-file', {path: url});
  108. response = response.data;
  109. }
  110. if (response.state == 'error') {
  111. throw new Error(response.error);
  112. }
  113. const workerId = response.workerId;
  114. if (!workerId)
  115. throw new Error('Неверный ответ api');
  116. response = await this.getWorkerStateFinish(workerId);
  117. if (response.state == 'error') {
  118. throw new Error(response.error);
  119. }
  120. if (response.size && estSize < 0) {
  121. estSize = response.size;
  122. }
  123. }
  124. return estSize;
  125. }
  126. async loadCachedBook(url, callback, estSize = -1) {
  127. if (!callback) callback = () => {};
  128. callback({state: 'loading', progress: 0});
  129. //получение размера файла
  130. if (estSize && estSize < 0) {
  131. estSize = await this.checkCachedBook(url);
  132. }
  133. //получение файла
  134. estSize = (estSize > 0 ? estSize : 1000000);
  135. const options = {
  136. onDownloadProgress: (progress) => {
  137. while (progress.loaded > estSize) estSize *= 1.5;
  138. if (callback)
  139. callback({progress: Math.round((progress.loaded*100)/estSize)});
  140. }
  141. }
  142. return await axios.get(url, options);
  143. }
  144. async uploadFile(file, maxUploadFileSize = 10*1024*1024, callback) {
  145. if (file.size > maxUploadFileSize)
  146. throw new Error(`Размер файла превышает ${maxUploadFileSize} байт`);
  147. let formData = new FormData();
  148. formData.append('file', file, file.name);
  149. const options = {
  150. headers: {
  151. 'Content-Type': 'multipart/form-data'
  152. },
  153. onUploadProgress: progress => {
  154. const total = (progress.total ? progress.total : progress.loaded + 200000);
  155. if (callback)
  156. callback({state: 'upload', progress: Math.round((progress.loaded*100)/total)});
  157. }
  158. };
  159. let response = await api.post('/upload-file', formData, options);
  160. if (response.data.state == 'error')
  161. throw new Error(response.data.error);
  162. const url = response.data.url;
  163. if (!url)
  164. throw new Error('Неверный ответ api');
  165. return url;
  166. }
  167. async storage(request) {
  168. let response = null;
  169. try {
  170. response = await wsc.message(await wsc.send({action: 'reader-storage', body: request}));
  171. } catch (e) {
  172. console.error(e);
  173. //если с WebSocket проблема, работаем по http
  174. response = await api.post('/storage', request);
  175. response = response.data;
  176. }
  177. const state = response.state;
  178. if (!state)
  179. throw new Error('Неверный ответ api');
  180. if (state == 'error') {
  181. throw new Error(response.error);
  182. }
  183. return response;
  184. }
  185. makeUrlFromBuf(buf) {
  186. const key = utils.toHex(cryptoUtils.sha256(buf));
  187. return `disk://${key}`;
  188. }
  189. async uploadFileBuf(buf, url) {
  190. if (!url)
  191. url = this.makeUrlFromBuf(buf);
  192. let response;
  193. try {
  194. await axios.head(url.replace('disk://', '/upload/'), {headers: {'Cache-Control': 'no-cache'}});
  195. response = await wsc.message(await wsc.send({action: 'upload-file-touch', url}));
  196. } catch (e) {
  197. response = await wsc.message(await wsc.send({action: 'upload-file-buf', buf}));
  198. }
  199. if (response.error)
  200. throw new Error(response.error);
  201. return response;
  202. }
  203. async getUploadedFileBuf(url) {
  204. url = url.replace('disk://', '/upload/');
  205. return (await axios.get(url)).data;
  206. }
  207. }
  208. export default new Reader();