Stackedit/src/services/providers/googleDriveWorkspaceProvider.js

560 lines
19 KiB
JavaScript
Raw Normal View History

2017-12-10 23:49:20 +00:00
import store from '../../store';
import googleHelper from './helpers/googleHelper';
import providerRegistry from './providerRegistry';
2017-12-23 18:25:14 +00:00
import providerUtils from './providerUtils';
2017-12-10 23:49:20 +00:00
import utils from '../utils';
2018-01-04 20:19:10 +00:00
let fileIdToOpen;
2018-02-01 22:39:14 +00:00
const getSyncData = (fileId) => {
const syncData = store.getters['data/syncDataByItemId'][`${fileId}/content`];
return syncData
? Promise.resolve(syncData)
: Promise.reject(); // No need for a proper error message.
};
2017-12-10 23:49:20 +00:00
export default providerRegistry.register({
id: 'googleDriveWorkspace',
getToken() {
2017-12-17 15:08:52 +00:00
return store.getters['workspace/syncToken'];
2017-12-10 23:49:20 +00:00
},
initWorkspace() {
2018-01-04 20:19:10 +00:00
const makeWorkspaceIdParams = folderId => ({
2017-12-17 15:08:52 +00:00
providerId: this.id,
folderId,
2018-01-04 20:19:10 +00:00
});
const makeWorkspaceId = folderId => folderId && utils.makeWorkspaceId(
makeWorkspaceIdParams(folderId));
2017-12-17 15:08:52 +00:00
2018-01-04 20:19:10 +00:00
const getWorkspace = folderId =>
store.getters['data/sanitizedWorkspaces'][makeWorkspaceId(folderId)];
2017-12-17 15:08:52 +00:00
2017-12-10 23:49:20 +00:00
const initFolder = (token, folder) => Promise.resolve({
2017-12-17 15:08:52 +00:00
folderId: folder.id,
2017-12-10 23:49:20 +00:00
dataFolderId: folder.appProperties.dataFolderId,
trashFolderId: folder.appProperties.trashFolderId,
})
.then((properties) => {
// Make sure data folder exists
if (properties.dataFolderId) {
return properties;
}
return googleHelper.uploadFile(
token,
'.stackedit-data',
[folder.id],
2017-12-17 15:08:52 +00:00
{ folderId: folder.id },
2017-12-10 23:49:20 +00:00
undefined,
2017-12-23 18:25:14 +00:00
googleHelper.folderMimeType,
2017-12-10 23:49:20 +00:00
)
.then(dataFolder => ({
...properties,
dataFolderId: dataFolder.id,
}));
})
.then((properties) => {
// Make sure trash folder exists
if (properties.trashFolderId) {
return properties;
}
return googleHelper.uploadFile(
token,
'.stackedit-trash',
[folder.id],
2017-12-17 15:08:52 +00:00
{ folderId: folder.id },
2017-12-10 23:49:20 +00:00
undefined,
2017-12-23 18:25:14 +00:00
googleHelper.folderMimeType,
2017-12-10 23:49:20 +00:00
)
.then(trashFolder => ({
...properties,
trashFolderId: trashFolder.id,
}));
})
.then((properties) => {
// Update workspace if some properties are missing
2017-12-17 15:08:52 +00:00
if (properties.folderId === folder.appProperties.folderId
2017-12-10 23:49:20 +00:00
&& properties.dataFolderId === folder.appProperties.dataFolderId
&& properties.trashFolderId === folder.appProperties.trashFolderId
) {
return properties;
}
return googleHelper.uploadFile(
token,
undefined,
undefined,
properties,
undefined,
2017-12-23 18:25:14 +00:00
googleHelper.folderMimeType,
2017-12-10 23:49:20 +00:00
folder.id,
)
.then(() => properties);
})
.then((properties) => {
// Update workspace in the store
2017-12-17 15:08:52 +00:00
const workspaceId = makeWorkspaceId(folder.id);
2017-12-10 23:49:20 +00:00
store.dispatch('data/patchWorkspaces', {
2017-12-17 15:08:52 +00:00
[workspaceId]: {
id: workspaceId,
2017-12-10 23:49:20 +00:00
sub: token.sub,
name: folder.name,
providerId: this.id,
2018-01-04 20:19:10 +00:00
url: location.href,
2017-12-10 23:49:20 +00:00
folderId: folder.id,
2018-04-11 09:17:53 +00:00
teamDriveId: folder.teamDriveId,
2017-12-10 23:49:20 +00:00
dataFolderId: properties.dataFolderId,
trashFolderId: properties.trashFolderId,
},
});
2017-12-17 15:08:52 +00:00
// Return the workspace
2018-01-24 07:31:54 +00:00
return store.getters['data/sanitizedWorkspaces'][workspaceId];
2017-12-10 23:49:20 +00:00
});
2017-12-17 15:08:52 +00:00
return Promise.resolve()
.then(() => {
2018-01-04 20:19:10 +00:00
const workspace = getWorkspace(utils.queryParams.folderId);
2017-12-17 15:08:52 +00:00
// See if we already have a token
const googleTokens = store.getters['data/googleTokens'];
// Token sub is in the workspace or in the url if workspace is about to be created
const token = workspace ? googleTokens[workspace.sub] : googleTokens[utils.queryParams.sub];
2017-12-23 18:25:14 +00:00
if (token && token.isDrive && token.driveFullAccess) {
2017-12-17 15:08:52 +00:00
return token;
}
// If no token has been found, popup an authorize window and get one
return store.dispatch('modal/workspaceGoogleRedirection', {
2018-01-04 20:19:10 +00:00
onResolve: () => googleHelper.addDriveAccount(true, utils.queryParams.sub),
2017-12-17 15:08:52 +00:00
});
})
2017-12-10 23:49:20 +00:00
.then(token => Promise.resolve()
2017-12-17 15:08:52 +00:00
// If no folderId is provided, create one
2017-12-10 23:49:20 +00:00
.then(() => utils.queryParams.folderId || googleHelper.uploadFile(
token,
'StackEdit workspace',
[],
undefined,
undefined,
2017-12-23 18:25:14 +00:00
googleHelper.folderMimeType,
2017-12-17 15:08:52 +00:00
)
.then(folder => initFolder(token, {
...folder,
appProperties: {},
})
.then(() => folder.id)))
// If workspace does not exist, initialize one
.then(folderId => getWorkspace(folderId) || googleHelper.getFile(token, folderId)
.then((folder) => {
2017-12-23 18:25:14 +00:00
folder.appProperties = folder.appProperties || {};
2017-12-17 15:08:52 +00:00
const folderIdProperty = folder.appProperties.folderId;
if (folderIdProperty && folderIdProperty !== folderId) {
2018-01-04 20:19:10 +00:00
throw new Error(`Folder ${folderId} is part of another workspace.`);
2017-12-17 15:08:52 +00:00
}
return initFolder(token, folder);
}, () => {
2017-12-23 18:25:14 +00:00
throw new Error(`Folder ${folderId} is not accessible. Make sure you have the right permissions.`);
2018-01-04 20:19:10 +00:00
}))
.then((workspace) => {
// Fix the URL hash
utils.setQueryParams(makeWorkspaceIdParams(workspace.folderId));
2018-01-24 07:31:54 +00:00
if (workspace.url !== location.href) {
store.dispatch('data/patchWorkspaces', {
[workspace.id]: {
...workspace,
url: location.href,
},
});
}
return store.getters['data/sanitizedWorkspaces'][workspace.id];
2018-01-04 20:19:10 +00:00
}));
},
performAction() {
return Promise.resolve()
.then(() => {
const state = googleHelper.driveState || {};
const token = this.getToken();
switch (token && state.action) {
case 'create':
return Promise.resolve()
.then(() => {
const driveFolder = googleHelper.driveActionFolder;
let syncData = store.getters['data/syncData'][driveFolder.id];
if (!syncData && driveFolder.appProperties.id) {
// Create folder if not already synced
store.commit('folder/setItem', {
id: driveFolder.appProperties.id,
name: driveFolder.name,
});
const item = store.state.folder.itemMap[driveFolder.appProperties.id];
syncData = {
id: driveFolder.id,
itemId: item.id,
type: item.type,
hash: item.hash,
};
store.dispatch('data/patchSyncData', {
[syncData.id]: syncData,
});
}
return store.dispatch('createFile', {
parentId: syncData && syncData.itemId,
})
.then((file) => {
store.commit('file/setCurrentId', file.id);
// File will be created on next workspace sync
});
});
case 'open':
return Promise.resolve()
.then(() => {
// open first file only
const firstFile = googleHelper.driveActionFiles[0];
const syncData = store.getters['data/syncData'][firstFile.id];
if (!syncData) {
fileIdToOpen = firstFile.id;
} else {
store.commit('file/setCurrentId', syncData.itemId);
}
});
default:
return null;
}
});
2017-12-10 23:49:20 +00:00
},
2017-12-23 18:25:14 +00:00
getChanges() {
const workspace = store.getters['workspace/currentWorkspace'];
const syncToken = store.getters['workspace/syncToken'];
2017-12-17 15:08:52 +00:00
const startPageToken = store.getters['data/localSettings'].syncStartPageToken;
2018-04-11 09:17:53 +00:00
return googleHelper.getChanges(syncToken, startPageToken, false, workspace.teamDriveId)
2017-12-10 23:49:20 +00:00
.then((result) => {
2017-12-23 18:25:14 +00:00
// Collect possible parent IDs
const parentIds = {};
Object.entries(store.getters['data/syncDataByItemId']).forEach(([id, syncData]) => {
parentIds[syncData.id] = id;
});
result.changes.forEach((change) => {
const id = ((change.file || {}).appProperties || {}).id;
if (id) {
parentIds[change.fileId] = id;
}
});
// Collect changes
const changes = [];
result.changes.forEach((change) => {
// Ignore changes on StackEdit own folders
if (change.fileId === workspace.folderId
|| change.fileId === workspace.dataFolderId
|| change.fileId === workspace.trashFolderId
) {
return;
}
let contentChange;
2017-12-10 23:49:20 +00:00
if (change.file) {
2017-12-23 18:25:14 +00:00
// Ignore changes in files that are not in the workspace
2018-01-04 20:19:10 +00:00
const appProperties = change.file.appProperties;
if (!appProperties || appProperties.folderId !== workspace.folderId
2017-12-23 18:25:14 +00:00
) {
return;
2017-12-10 23:49:20 +00:00
}
2017-12-23 18:25:14 +00:00
// If change is on a data item
if (change.file.parents[0] === workspace.dataFolderId) {
// Data item has a JSON as a filename
try {
change.item = JSON.parse(change.file.name);
} catch (e) {
return;
}
} else {
// Change on a file or folder
const type = change.file.mimeType === googleHelper.folderMimeType
? 'folder'
: 'file';
const item = {
2018-01-04 20:19:10 +00:00
id: appProperties.id,
2017-12-23 18:25:14 +00:00
type,
name: change.file.name,
parentId: null,
};
// Fill parentId
if (change.file.parents.some(parentId => parentId === workspace.trashFolderId)) {
item.parentId = 'trash';
} else {
change.file.parents.some((parentId) => {
if (!parentIds[parentId]) {
return false;
}
item.parentId = parentIds[parentId];
return true;
});
}
change.item = utils.addItemHash(item);
if (type === 'file') {
// create a fake change as a file content change
contentChange = {
item: {
2018-01-04 20:19:10 +00:00
id: `${appProperties.id}/content`,
2017-12-23 18:25:14 +00:00
type: 'content',
// Need a truthy value to force saving sync data
hash: 1,
},
syncData: {
id: `${change.fileId}/content`,
2018-01-04 20:19:10 +00:00
itemId: `${appProperties.id}/content`,
2017-12-23 18:25:14 +00:00
type: 'content',
// Need a truthy value to force downloading the content
hash: 1,
},
syncDataId: `${change.fileId}/content`,
};
}
}
2017-12-10 23:49:20 +00:00
// Build sync data
change.syncData = {
id: change.fileId,
itemId: change.item.id,
type: change.item.type,
hash: change.item.hash,
};
2017-12-23 18:25:14 +00:00
} else {
// Item was removed
const syncData = store.getters['data/syncData'][change.fileId];
if (syncData && syncData.type === 'file') {
// create a fake change as a file content change
contentChange = {
syncDataId: `${change.fileId}/content`,
};
}
2017-12-10 23:49:20 +00:00
}
2017-12-23 18:25:14 +00:00
// Push change
2017-12-17 15:08:52 +00:00
change.syncDataId = change.fileId;
2017-12-23 18:25:14 +00:00
changes.push(change);
if (contentChange) {
changes.push(contentChange);
}
2017-12-10 23:49:20 +00:00
});
2017-12-17 15:08:52 +00:00
changes.startPageToken = result.startPageToken;
2017-12-10 23:49:20 +00:00
return changes;
});
},
2017-12-23 18:25:14 +00:00
setAppliedChanges(changes) {
store.dispatch('data/patchLocalSettings', {
syncStartPageToken: changes.startPageToken,
});
},
saveSimpleItem(item, syncData, ifNotTooLate) {
return Promise.resolve()
.then(() => {
const workspace = store.getters['workspace/currentWorkspace'];
const syncToken = store.getters['workspace/syncToken'];
if (item.type !== 'file' && item.type !== 'folder') {
return googleHelper.uploadFile(
syncToken,
JSON.stringify(item),
[workspace.dataFolderId],
{
folderId: workspace.folderId,
},
undefined,
undefined,
syncData && syncData.id,
ifNotTooLate,
);
}
// Type `file` or `folder`
const parentSyncData = store.getters['data/syncDataByItemId'][item.parentId];
return googleHelper.uploadFile(
syncToken,
item.name,
[parentSyncData ? parentSyncData.id : workspace.folderId],
{
id: item.id,
folderId: workspace.folderId,
},
undefined,
item.type === 'folder' ? googleHelper.folderMimeType : undefined,
syncData && syncData.id,
ifNotTooLate,
);
})
.then(file => ({
// Build sync data
id: file.id,
itemId: item.id,
type: item.type,
hash: item.hash,
}));
},
removeItem(syncData, ifNotTooLate) {
// Ignore content deletion
if (syncData.type === 'content') {
return Promise.resolve();
}
const syncToken = store.getters['workspace/syncToken'];
return googleHelper.removeFile(syncToken, syncData.id, ifNotTooLate);
},
downloadContent(token, syncLocation) {
const syncData = store.getters['data/syncDataByItemId'][syncLocation.fileId];
const contentSyncData = store.getters['data/syncDataByItemId'][`${syncLocation.fileId}/content`];
if (!syncData || !contentSyncData) {
return Promise.resolve();
}
return googleHelper.downloadFile(token, syncData.id)
.then((content) => {
2018-01-30 07:36:33 +00:00
const item = providerUtils.parseContent(content, `${syncLocation.fileId}/content`);
2017-12-23 18:25:14 +00:00
if (item.hash !== contentSyncData.hash) {
store.dispatch('data/patchSyncData', {
[contentSyncData.id]: {
...contentSyncData,
hash: item.hash,
},
});
}
2018-01-04 20:19:10 +00:00
// Open the file requested by action if it was to synced yet
if (fileIdToOpen && fileIdToOpen === syncData.id) {
fileIdToOpen = null;
// Open the file once downloaded content has been stored
setTimeout(() => {
store.commit('file/setCurrentId', syncData.itemId);
}, 10);
}
2017-12-23 18:25:14 +00:00
return item;
});
},
downloadData(dataId) {
const syncData = store.getters['data/syncDataByItemId'][dataId];
if (!syncData) {
return Promise.resolve();
}
const syncToken = store.getters['workspace/syncToken'];
return googleHelper.downloadFile(syncToken, syncData.id)
.then((content) => {
const item = JSON.parse(content);
if (item.hash !== syncData.hash) {
store.dispatch('data/patchSyncData', {
[syncData.id]: {
...syncData,
hash: item.hash,
},
});
}
return item;
});
},
uploadContent(token, content, syncLocation, ifNotTooLate) {
const contentSyncData = store.getters['data/syncDataByItemId'][`${syncLocation.fileId}/content`];
if (contentSyncData && contentSyncData.hash === content.hash) {
2018-02-01 22:39:14 +00:00
return Promise.resolve(syncLocation);
2017-12-23 18:25:14 +00:00
}
return Promise.resolve()
.then(() => {
const syncData = store.getters['data/syncDataByItemId'][syncLocation.fileId];
if (syncData) {
// Only update file media
return googleHelper.uploadFile(
token,
undefined,
undefined,
undefined,
providerUtils.serializeContent(content),
undefined,
syncData.id,
ifNotTooLate,
);
}
// Create file with media
const workspace = store.getters['workspace/currentWorkspace'];
// Use deepCopy to freeze objects
const item = utils.deepCopy(store.state.file.itemMap[syncLocation.fileId]);
const parentSyncData = store.getters['data/syncDataByItemId'][item.parentId];
return googleHelper.uploadFile(
token,
item.name,
[parentSyncData ? parentSyncData.id : workspace.folderId],
{
id: item.id,
folderId: workspace.folderId,
},
providerUtils.serializeContent(content),
undefined,
undefined,
ifNotTooLate,
)
.then((file) => {
store.dispatch('data/patchSyncData', {
[file.id]: {
id: file.id,
itemId: item.id,
type: item.type,
hash: item.hash,
},
});
return file;
});
})
.then(file => store.dispatch('data/patchSyncData', {
[`${file.id}/content`]: {
// Build sync data
id: `${file.id}/content`,
itemId: content.id,
type: content.type,
hash: content.hash,
},
}))
.then(() => syncLocation);
},
uploadData(item, dataId, ifNotTooLate) {
const syncData = store.getters['data/syncDataByItemId'][dataId];
if (syncData && syncData.hash === item.hash) {
return Promise.resolve();
}
const workspace = store.getters['workspace/currentWorkspace'];
const syncToken = store.getters['workspace/syncToken'];
return googleHelper.uploadFile(
syncToken,
JSON.stringify({
id: item.id,
type: item.type,
hash: item.hash,
}),
[workspace.dataFolderId],
{
folderId: workspace.folderId,
},
JSON.stringify(item),
undefined,
syncData && syncData.id,
ifNotTooLate,
)
.then(file => store.dispatch('data/patchSyncData', {
[file.id]: {
// Build sync data
id: file.id,
itemId: item.id,
type: item.type,
hash: item.hash,
},
}));
},
listRevisions(token, fileId) {
2018-02-01 22:39:14 +00:00
return getSyncData(fileId)
.then(syncData => googleHelper.getFileRevisions(token, syncData.id))
2017-12-23 18:25:14 +00:00
.then(revisions => revisions.map(revision => ({
id: revision.id,
sub: revision.lastModifyingUser && revision.lastModifyingUser.permissionId,
created: new Date(revision.modifiedTime).getTime(),
2018-01-30 07:36:33 +00:00
}))
.sort((revision1, revision2) => revision2.created - revision1.created));
2017-12-23 18:25:14 +00:00
},
getRevisionContent(token, fileId, revisionId) {
2018-02-01 22:39:14 +00:00
return getSyncData(fileId)
.then(syncData => googleHelper.downloadFileRevision(token, syncData.id, revisionId))
2018-01-30 07:36:33 +00:00
.then(content => providerUtils.parseContent(content, `${fileId}/content`));
2017-12-23 18:25:14 +00:00
},
2017-12-10 23:49:20 +00:00
});