Skip to content

Commit d308f36

Browse files
committed
feat: save file locally when receiving postFile message
1 parent ba4627a commit d308f36

File tree

14 files changed

+139
-1
lines changed

14 files changed

+139
-1
lines changed

public/app/config/channels.js

+1
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,5 @@ module.exports = {
7171
GET_SPACE_IN_CLASSROOM_CHANNEL: 'classroom:space:get',
7272
LOAD_SPACE_IN_CLASSROOM_CHANNEL: 'classroom:space:load',
7373
GET_SPACE_TO_LOAD_IN_CLASSROOM_CHANNEL: 'classroom:space:load:get-space',
74+
POST_FILE_CHANNEL: 'file:post',
7475
};

public/app/config/config.js

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// eslint-disable-next-line import/no-extraneous-dependencies
22
const { app } = require('electron');
3+
const ObjectId = require('bson-objectid');
34
const isWindows = require('../utils/isWindows');
45

56
// types that we support downloading
@@ -84,6 +85,12 @@ const ACTIONS_VERBS = {
8485
LOGOUT: 'logout',
8586
};
8687

88+
const buildFilesPath = ({ userId, spaceId, name }) => {
89+
// add generated id to handle duplicate files
90+
const generatedId = ObjectId().str;
91+
return `${VAR_FOLDER}/${spaceId}/files/${userId}/${generatedId}_${name}`;
92+
};
93+
8794
module.exports = {
8895
DEFAULT_LOGGING_LEVEL,
8996
DEFAULT_PROTOCOL,
@@ -110,4 +117,5 @@ module.exports = {
110117
VISIBILITIES,
111118
DEFAULT_FORMAT,
112119
ACTIONS_VERBS,
120+
buildFilesPath,
113121
};

public/app/config/messages.js

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ const ERROR_GETTING_SPACE_IN_CLASSROOM_MESSAGE =
102102
const ERROR_SETTING_ACTION_ACCESSIBILITY =
103103
'There was an error setting the action accessibility';
104104
const ERROR_SETTING_ACTIONS_AS_ENABLED = 'There was an error enabling actions';
105+
const ERROR_POSTING_FILE_MESSAGE = 'There was an error uploading the file';
105106

106107
module.exports = {
107108
ERROR_GETTING_DEVELOPER_MODE,
@@ -172,4 +173,5 @@ module.exports = {
172173
ERROR_INVALID_USERNAME_MESSAGE,
173174
ERROR_SETTING_ACTION_ACCESSIBILITY,
174175
ERROR_SETTING_ACTIONS_AS_ENABLED,
176+
ERROR_POSTING_FILE_MESSAGE,
175177
};

public/app/listeners/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const loadSpaceInClassroom = require('./loadSpaceInClassroom');
5050
const setActionAccessibility = require('./setActionAccessibility');
5151
const setActionsAsEnabled = require('./setActionsAsEnabled');
5252
const windowAllClosed = require('./windowAllClosed');
53+
const postFile = require('./postFile');
5354

5455
module.exports = {
5556
loadSpace,
@@ -103,4 +104,5 @@ module.exports = {
103104
setActionAccessibility,
104105
setActionsAsEnabled,
105106
windowAllClosed,
107+
postFile,
106108
};

public/app/listeners/postFile.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const fs = require('fs');
2+
const { POST_FILE_CHANNEL } = require('../config/channels');
3+
const { buildFilesPath } = require('../config/config');
4+
const { ensureDirectoryExistence } = require('../utilities');
5+
const logger = require('../logger');
6+
const { ERROR_GENERAL } = require('../config/errors');
7+
8+
const postFile = mainWindow => (event, payload = {}) => {
9+
try {
10+
const { userId, spaceId, data } = payload;
11+
12+
// download file given path
13+
const { path, name } = data;
14+
15+
const savePath = buildFilesPath({ userId, spaceId, name });
16+
ensureDirectoryExistence(savePath);
17+
fs.copyFile(path, savePath, err => {
18+
if (err) {
19+
throw err;
20+
}
21+
logger.debug(`the file ${name} was uploaded`);
22+
});
23+
24+
// update data
25+
const newData = { name, uri: `file://${savePath}` };
26+
const newPayload = { ...payload, data: newData };
27+
28+
// send back the resource
29+
mainWindow.webContents.send(POST_FILE_CHANNEL, newPayload);
30+
} catch (e) {
31+
console.error(e);
32+
mainWindow.webContents.send(POST_FILE_CHANNEL, ERROR_GENERAL);
33+
}
34+
};
35+
36+
module.exports = postFile;

public/app/utilities.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const mkdirp = require('mkdirp');
22
const rimraf = require('rimraf');
3+
const path = require('path');
34
const { promisify } = require('util');
45
const mime = require('mime-types');
56
const md5 = require('md5');
@@ -135,8 +136,21 @@ const clean = async dir => {
135136
return promisify(rimraf)(dir);
136137
};
137138

139+
/**
140+
* Ensure directories for given filepath exist
141+
*/
142+
function ensureDirectoryExistence(filePath) {
143+
const dirname = path.dirname(filePath);
144+
if (fs.existsSync(dirname)) {
145+
return true;
146+
}
147+
ensureDirectoryExistence(dirname);
148+
return fs.mkdirSync(dirname);
149+
}
150+
138151
module.exports = {
139152
clean,
153+
ensureDirectoryExistence,
140154
performFileSystemOperation,
141155
getExtension,
142156
isDownloadable,

public/electron.js

+6
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const {
7474
LOAD_SPACE_IN_CLASSROOM_CHANNEL,
7575
SET_ACTION_ACCESSIBILITY_CHANNEL,
7676
SET_ACTIONS_AS_ENABLED_CHANNEL,
77+
POST_FILE_CHANNEL,
7778
} = require('./app/config/channels');
7879
const env = require('./env.json');
7980
const {
@@ -127,6 +128,7 @@ const {
127128
setActionAccessibility,
128129
setActionsAsEnabled,
129130
windowAllClosed,
131+
postFile,
130132
} = require('./app/listeners');
131133
const isMac = require('./app/utils/isMac');
132134

@@ -488,6 +490,10 @@ app.on('ready', async () => {
488490

489491
// called when creating an action
490492
ipcMain.on(POST_ACTION_CHANNEL, postAction(mainWindow, db));
493+
494+
// called when creating a file
495+
ipcMain.on(POST_FILE_CHANNEL, postFile(mainWindow, db));
496+
491497
// called when logging in a user
492498
ipcMain.on(SIGN_IN_CHANNEL, signIn(mainWindow, db));
493499

src/actions/file.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { POST_FILE_CHANNEL } from '../config/channels';
2+
import { POST_FILE_SUCCEEDED, POST_FILE_FAILED } from '../types';
3+
import { ERROR_GENERAL } from '../config/errors';
4+
import { ERROR_POSTING_FILE_MESSAGE } from '../config/messages';
5+
6+
// eslint-disable-next-line import/prefer-default-export
7+
export const postFile = async (
8+
{ userId, appInstanceId, spaceId, subSpaceId, format, data, type } = {},
9+
callback
10+
) => () => {
11+
try {
12+
window.ipcRenderer.send(POST_FILE_CHANNEL, {
13+
userId,
14+
appInstanceId,
15+
spaceId,
16+
subSpaceId,
17+
format,
18+
type,
19+
data,
20+
});
21+
22+
window.ipcRenderer.once(POST_FILE_CHANNEL, async (event, response) => {
23+
if (response === ERROR_GENERAL) {
24+
callback({
25+
appInstanceId,
26+
type: POST_FILE_FAILED,
27+
payload: ERROR_POSTING_FILE_MESSAGE,
28+
});
29+
} else {
30+
callback({
31+
// have to include the appInstanceId to avoid broadcasting
32+
appInstanceId,
33+
type: POST_FILE_SUCCEEDED,
34+
payload: response,
35+
});
36+
}
37+
});
38+
} catch (err) {
39+
console.error(err);
40+
}
41+
};

src/actions/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export * from './syncSpace';
1111
export * from './loadSpace';
1212
export * from './exportSpace';
1313
export * from './classroom';
14+
export * from './file';

src/components/phase/PhaseApp.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ import {
1515
APP_INSTANCE_RESOURCE_TYPES,
1616
POST_ACTION,
1717
ACTION_TYPES,
18+
POST_FILE,
19+
FILE_TYPES,
1820
} from '../../types';
1921
import {
2022
getAppInstanceResources,
2123
patchAppInstanceResource,
2224
postAppInstanceResource,
2325
getAppInstance,
2426
postAction,
27+
postFile,
2528
} from '../../actions';
2629
import {
2730
DEFAULT_LANGUAGE,
@@ -51,6 +54,7 @@ class PhaseApp extends Component {
5154
folder: PropTypes.string.isRequired,
5255
dispatchGetAppInstance: PropTypes.func.isRequired,
5356
dispatchPostAction: PropTypes.func.isRequired,
57+
dispatchPostFile: PropTypes.func.isRequired,
5458
id: PropTypes.string.isRequired,
5559
phaseId: PropTypes.string.isRequired,
5660
spaceId: PropTypes.string.isRequired,
@@ -115,6 +119,7 @@ class PhaseApp extends Component {
115119
try {
116120
const {
117121
dispatchGetAppInstance,
122+
dispatchPostFile,
118123
appInstance,
119124
dispatchPostAction,
120125
user,
@@ -125,7 +130,13 @@ class PhaseApp extends Component {
125130
const { id: componentAppInstanceId } = appInstance || {};
126131
const { type, payload } = JSON.parse(event.data);
127132
let { id: messageAppInstanceId } = payload;
128-
if ([...APP_INSTANCE_RESOURCE_TYPES, ...ACTION_TYPES].includes(type)) {
133+
if (
134+
[
135+
...APP_INSTANCE_RESOURCE_TYPES,
136+
...ACTION_TYPES,
137+
...FILE_TYPES,
138+
].includes(type)
139+
) {
129140
({ appInstanceId: messageAppInstanceId } = payload);
130141
}
131142

@@ -153,6 +164,12 @@ class PhaseApp extends Component {
153164
}
154165
break;
155166
}
167+
case POST_FILE: {
168+
if (isSpaceSaved) {
169+
return dispatchPostFile(payload, this.postMessage);
170+
}
171+
break;
172+
}
156173
default:
157174
return false;
158175
}
@@ -324,6 +341,7 @@ const mapStateToProps = ({ authentication, Space }) => ({
324341
const mapDispatchToProps = {
325342
dispatchGetAppInstance: getAppInstance,
326343
dispatchPostAction: postAction,
344+
dispatchPostFile: postFile,
327345
};
328346

329347
const ConnectedComponent = connect(

src/config/channels.js

+1
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,5 @@ module.exports = {
7171
GET_SPACE_IN_CLASSROOM_CHANNEL: 'classroom:space:get',
7272
LOAD_SPACE_IN_CLASSROOM_CHANNEL: 'classroom:space:load',
7373
GET_SPACE_TO_LOAD_IN_CLASSROOM_CHANNEL: 'classroom:space:load:get-space',
74+
POST_FILE_CHANNEL: 'file:post',
7475
};

src/config/messages.js

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ const ERROR_INVALID_USERNAME_MESSAGE = 'This username is invalid';
102102
const ERROR_SETTING_ACTION_ACCESSIBILITY =
103103
'There was an error setting the action accessibility';
104104
const ERROR_SETTING_ACTIONS_AS_ENABLED = 'There was an error enabling actions';
105+
const ERROR_POSTING_FILE_MESSAGE = 'There was an error uploading the file';
105106

106107
module.exports = {
107108
ERROR_GETTING_DEVELOPER_MODE,
@@ -172,4 +173,5 @@ module.exports = {
172173
ERROR_INVALID_USERNAME_MESSAGE,
173174
ERROR_SETTING_ACTION_ACCESSIBILITY,
174175
ERROR_SETTING_ACTIONS_AS_ENABLED,
176+
ERROR_POSTING_FILE_MESSAGE,
175177
};

src/types/file.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const POST_FILE = 'POST_FILE';
2+
export const POST_FILE_SUCCEEDED = 'POST_FILE_SUCCEEDED';
3+
export const POST_FILE_FAILED = 'POST_FILE_FAILED';
4+
5+
export const FILE_TYPES = [POST_FILE, POST_FILE_SUCCEEDED, POST_FILE_FAILED];

src/types/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export * from './syncSpace';
1111
export * from './loadSpace';
1212
export * from './exportSpace';
1313
export * from './classroom';
14+
export * from './file';

0 commit comments

Comments
 (0)