-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit, functional Google Drive sync, missing deletion/renami…
…ng support This implements Google Drive support for both the web app implicit Oauth2 flow (1 hour token timeout, drive.file scope restricted to files/folders created by the plugin) and the offline web server OAuth2 flow (1 hour token timeout but with transparent token refresh and full Google Drive access if using the drive scope). The web app implicit flow uses the published web app OAuth2 profile "Obsidian Google Drive Plugin", which uses a drive scope. This profile uses a localhost redirect URI but the plugin doesn't set any server, so a successful authorization causes a browser error page about localhost not responding. That page still has the localhost URL, which can then be copied to plugin login dialog box and then the token extracted, thus allowing Google Drive access. The offline web server flow is only used with a personalized Google Drive login configuration, since it needs the client secret which is not provided with the default Google Drive login configuration included. Full vault synchronization from/to Obsidian vault to/from Google Drive is supported by using the ribbon or palette command. - Newer files are uploaded/downloaded, older files are overwritten, new folders are created. - There's no deletion support yet, so deletes don't propagate to Google Drive or vaults and the next synchronization will re-download the locally deleted file. This also means that renames will cause the next synchronization to re-download the file/folder with the old name. - There's no conflict resolution warning so if files are modified by two different devices without an intervening synchronization, the newer modification will silently prevail. Files: LICENSE README.md - Usual stuff esbuild.config.mjs - Added some artifact copying to simplify development. main.ts - Implemented offline web server OAuth2 flow, this is the default flow, but has a one hour token timeout after which it needs to be manually re authorized. - Implemented web app implicit OAuth2 flow, this flow is only used if a client secret is supplied in the Google Drive login dialog box. This allows overcoming the one hour token timeout by refreshing the token (which needs a client secret). - Implemented Google Drive access, by default using the drive.file scope so only files and folders created by this plugin can be accessed, but providing a drive scope in personalized credentials, the whole Google Drive (including orphan folders like "Computers") can be accessed. - Implemented UI for Google Drive folder listing and creation. - Implemented debug information via a scrollable Notice. - Implemented login modal with advanced configuraiton options - Implemented settings modal - Implemented a text input modal that allows entering text with ok/cancel manifest.json package.json - Filled in with plugin version and name releases/0.1.0/main.js releases/0.1.0/manifest.json - This release tsconfig.json version-bump.mjs versions.json - Usual stuff Tests: - Tested on Android - Tested on PC - Synchronized existing vault to new Google Drive folder - Synchronized new empty vault from existing Google Drive folder - Modified vault and synchronized to Google Drive folder - Tested upload only/download only/upload and download synchronization modes - Synchronized using default credentials, verified token expires and requires re-authorization, only folders/files created by the plugin are accessible. - Synchronized using personalized credentials with client secret, verified token is refreshed transparently - Synchronized using personalized credentials with client secret and drive scope, verified the whole Google Drive including orphan folders is accessible
- Loading branch information
0 parents
commit 7672aa7
Showing
16 changed files
with
3,809 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# top-most EditorConfig file | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = tab | ||
indent_size = 4 | ||
tab_width = 4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules/ | ||
|
||
main.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"root": true, | ||
"parser": "@typescript-eslint/parser", | ||
"env": { "node": true }, | ||
"plugins": [ | ||
"@typescript-eslint" | ||
], | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/eslint-recommended", | ||
"plugin:@typescript-eslint/recommended" | ||
], | ||
"parserOptions": { | ||
"sourceType": "module" | ||
}, | ||
"rules": { | ||
"no-unused-vars": "off", | ||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], | ||
"@typescript-eslint/ban-ts-comment": "off", | ||
"no-prototype-builtins": "off", | ||
"@typescript-eslint/no-empty-function": "off" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# vscode | ||
.vscode | ||
|
||
# Intellij | ||
*.iml | ||
.idea | ||
|
||
# npm | ||
node_modules | ||
|
||
# Don't include the compiled main.js file in the repo. | ||
# They should be uploaded to GitHub releases instead. | ||
/main.js | ||
|
||
# Exclude sourcemaps | ||
*.map | ||
|
||
# obsidian | ||
data.json | ||
|
||
# Exclude macOS Finder (System Explorer) View States | ||
.DS_Store | ||
|
||
# Exclude temp output dir | ||
_out/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
tag-version-prefix="" |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# Obsidian Google Drive Plugin | ||
|
||
[Obsidian](https://obsidian.md) plugin that allows synchronizing vaults from and to your [Google Drive](https://www.google.com/drive/) account. | ||
|
||
There are other ways of synchronizing Obsidian vaults to Google Drive but they require installing third party apps, while this is a standalone native Obsidian implementation. | ||
|
||
**Note there's no deletion/renaming support yet, also there's no conflict detection warning either, ie if you modify a vault from two different devices without synchronizing to Google Drive and then synchronize both to Google Drive, the oldest modification will be overwritten without warning on both the local vaults and Google Drive. See [TODO](#todo)** | ||
|
||
## Features | ||
- Google OAuth2 secure Google Drive authorization | ||
- No third party server access other than Google Drive | ||
- One hour Google Drive login timeout, if you synchronize your Obsidian vault with Google Drive after the timeout you will be prompted to login again (this is a limitation of not using third party servers, may change in the future) | ||
- Google Drive folder creation/browsing to synchronize the Obsidian vault to | ||
- Synchronize Obsidian vault from/to Google Drive | ||
- Upload files/folders from Obsidian vault to Google Drive | ||
- Download files/folders from Google Drive to Obsidian vault | ||
- Overwrite older files in Obsidian vault or Google Drive with the newer ones | ||
- _Upload only_, _download only_, _upload and download_ synchronization modes | ||
- With the plugin's default configuration settings, Google Drive access is limited to files and folders created by this plugin, can't access the rest of your Google Drive | ||
- Android support (untested on iOS, may work) | ||
- PC support (untested on Mac, may work) | ||
- Supports personalized Google Cloud API settings, when using those: | ||
- Supports all Google Drive access (not just files and folders created by this plugin) | ||
- Supports Google Drive _Computer_ folders (ie orphan folders) | ||
- Supports Google Drive transparent token refresh after the one hour Google Drive login timeout | ||
|
||
## Usage | ||
|
||
### First Obsidian vault instance | ||
|
||
1. Create and populate a vault inside Obsidian as usual or use an existing vault | ||
1. Configure Google Drive Plugin settings, specifically | ||
1. Configure Google Drive login | ||
1. Leave the default settings, click on the _Click to login to Google Drive_ link. | ||
1. A browser window using your default browser will open requiring your user and password to access Google Drive on behalf of _Obsidian Google Drive Plugin_ | ||
1. After filling in your password and accepting, you will be redirected to an error URL that starts with _localhost:_, copy that whole URL | ||
1. Go back to Obsidian Google Drive login dialog box and paste that URL into the _Paste error URL_ text box and press Ok | ||
1. Now that you have configured Google Drive login, create a Google Drive folder for your vault by choosing _\<new>_ in the _Google Drive folder_ dropdown | ||
1. Enter the folder name in the dialog box | ||
1. The dropdown will update and the folder is now your Google Drive vault folder | ||
1. Configure the rest of the Google Drive Plugin settings as desired and dismiss the configuration dialog | ||
1. Manually synchronize the vault with Google Drive whenever needed by pressing the _Synchronize Vault to Google Drive_ ribbon icon or the command _Google Drive: Synchronize vault_ | ||
1. Edit your notes as usual, synchronize with Google Drive as desired | ||
|
||
### Second and later Obsidian vault instances | ||
1. Create an empty vault inside Obsidian | ||
1. Configure the plugin settings as with the [first instance](#first-obsidian-vault-instance) but choose an existing Google Drive folder instead of creating a new one. | ||
1. Manually synchronize the vault with Google Drive by pressing the _Synchronize Vault to Google Drive_ ribbon icon or the command _Google Drive: Synchronize vault_, this will download all the files in that Google Drive folder into your Obsidian vault | ||
1. Edit your notes as usual, synchronize with Google Drive as desired | ||
|
||
## Google Drive login settings | ||
|
||
### Default Google Drive login settings | ||
|
||
The default settings have the following limitations: | ||
- Can only access files and folders created from the plugin. | ||
- Will periodically timeout and force to enter username and password in the browser to re-authorize the plugin | ||
|
||
### Personalized Google Drive login settings | ||
|
||
If you have your own Google Cloud developer account, you can use your own settings (client id, secret, scope and uri) in the Google Drive login configuration and | ||
- Bypass the one hour limit | ||
- Access your whole Google Drive if you use a drive scope | ||
|
||
## Privacy | ||
|
||
The plugin doesn't collect any kind of information, Google may collect whatever default statistics Google Cloud collects when you authorize applications to access and operate Google Drive. | ||
|
||
The plugin doesn't communicate to any server other than Google's to access your Google Drive files. | ||
|
||
The default (non personalized) settings only have access to files and folders created by this plugin. | ||
|
||
|
||
## TODO | ||
- Support file/folder deletion/renaming | ||
- Support conflict detection | ||
- Support periodic syncs | ||
- Support encryption | ||
- Support compression | ||
- Support for >5MB files | ||
- Better error checking |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import esbuild, { build } from "esbuild"; | ||
import process from "process"; | ||
import builtins from "builtin-modules"; | ||
import fs from "fs"; | ||
import path from "path"; | ||
|
||
function copyArtifactsToDirectory(artifacts, dirpath) { | ||
console.log("Copying artifacts to dirpath", dirpath); | ||
//console.debug("Creating dir", dirpath); | ||
fs.mkdirSync(dirpath, { recursive: true }); | ||
for (let artifact of artifacts) { | ||
const outFilepath = path.join(dirpath, artifact); | ||
//console.debug("copying", artifact, "to", outFilepath); | ||
fs.copyFileSync(artifact, outFilepath); | ||
} | ||
} | ||
|
||
function copyArtifactsToVaults(artifacts) { | ||
if (process.platform != "win32") { | ||
// XXX configFilePath is only implemented for Win32, other OSs not | ||
// supported yet | ||
console.warn("Copying artifacts to vaults only supported on Windows, copy manually"); | ||
return; | ||
} | ||
let configFilepath = path.join(process.env.APPDATA , "obsidian", "obsidian.json"); | ||
let configData = fs.readFileSync(configFilepath, "utf8"); | ||
let config = JSON.parse(configData); | ||
|
||
let manifestData = fs.readFileSync("manifest.json", "utf8"); | ||
let manifest = JSON.parse(manifestData); | ||
|
||
for (let id in config.vaults) { | ||
let vault = config.vaults[id]; | ||
if (vault.path.endsWith(path.sep + "Obsidian Sandbox")) { | ||
//console.debug("Ignoring sandbox"); | ||
continue; | ||
} | ||
let dirpath = path.join(vault.path, ".obsidian", "plugins", manifest.id); | ||
copyArtifactsToDirectory(artifacts, dirpath); | ||
} | ||
} | ||
|
||
const banner = | ||
`/* | ||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD | ||
if you want to view the source, please visit the github repository of this plugin | ||
*/ | ||
`; | ||
|
||
const prod = (process.argv[2] === "production"); | ||
|
||
const context = await esbuild.context({ | ||
banner: { | ||
js: banner, | ||
}, | ||
entryPoints: ["main.ts"], | ||
bundle: true, | ||
external: [ | ||
"obsidian", | ||
"electron", | ||
"@codemirror/autocomplete", | ||
"@codemirror/collab", | ||
"@codemirror/commands", | ||
"@codemirror/language", | ||
"@codemirror/lint", | ||
"@codemirror/search", | ||
"@codemirror/state", | ||
"@codemirror/view", | ||
"@lezer/common", | ||
"@lezer/highlight", | ||
"@lezer/lr", | ||
...builtins], | ||
format: "cjs", | ||
target: "es2018", | ||
// XXX Make build build both prod and dev, copy prod to release, dev to | ||
// vaults so it can be easily debugged on developer tools? | ||
// Probably easier done by two invocations from build.bat? | ||
// minify: prod, | ||
logLevel: "info", | ||
sourcemap: prod ? false : "inline", | ||
treeShaking: true, | ||
outfile: "main.js", | ||
}); | ||
|
||
let artifacts = ["main.js", "manifest.json"]; | ||
|
||
if (prod) { | ||
let buildResult = await context.rebuild(); | ||
if ((buildResult.errors.length == 0) && (buildResult.warnings.length == 0)) { | ||
copyArtifactsToVaults(artifacts); | ||
|
||
let manifestData = fs.readFileSync("manifest.json", "utf8"); | ||
let manifest = JSON.parse(manifestData); | ||
copyArtifactsToDirectory(artifacts, path.join("releases", manifest.version)); | ||
} | ||
process.exit(0); | ||
} else { | ||
await context.watch(); | ||
} |
Oops, something went wrong.