From 174c73994848fa37e4803ae9352e590a61bba8df Mon Sep 17 00:00:00 2001 From: Shan Khan Date: Mon, 27 Aug 2018 02:46:37 +0500 Subject: [PATCH] V3.1.0 (#611) * german localization * corrected naming of locale * german localization * corrected naming of locale * fix: missing partial i18n translation * fix: missing partial i18n translation * fix: add missing key for German language file * fix: typo * Minor Wording Changes The text on some parts looked strange and not correct, thought I'd update a few for grammar and looks. * chore: update .gitignore * chore: add tslint and prettier rules * chore: add format script * chore: update teslint rules to make it defined mutilple class in one file * chore: run tslint before publish extension * refactor: format the code with prettier * chore: update tsconfig and tslint * refactor: refactor utils.ts * refactor: refactor localize.ts * refactor: refactor setting.ts * refactor: refactor environmentPath.ts * refactor: refactor fileService.ts * chore: update teslint rules * refactor: replace github package with @octokit/rest * chore: update teslint rules * refactor: refactor commons.ts * refactor: refactor pluginService.ts * refactor: extension.ts * feat: replace fs module with fs-extra, and use async await instead of callback style * feat: update deps for vscode/typescript/adm-zip/tslint * refactor: refactor callback style with async await * remove unused dependencies ncp and rimraf, use fs-extra instead * fix: invalid @octokit/rest constructor options * chore: update travis script * fix: lstat should be async no sync * fix: user agent for proxy * pref: improve performance when start up. use async instead of sync method #472 * fix: implement promisify and fix lockfile * feat: use lockfile instead of proper-lockfile. * pref: improve performance * refactor: add sync class and clean up extension file. it should improve startup performance #472 * refactor: add lockfile.ts * fix: typo ShowSummaryOutput * refactor: refactor download function * fix: lockfile * refactor: rename 'en' to 'env' * refactor: remove unused activationEvents * refactor: refactor upload logic, make it clean * style: rename lockfile function name, make it same style with other * refactor: sync advance options * chore: not allow unuser locals and params. make it clean * refactor: remove unused local and params * refactor: remove unneed await * pref: improve localize performance, init when extension be actived * fix: make sure resolve language merger with default language. * Update tutorial message * Fix slack img * Update path to imgur link * Update tutorial link again * Extension Installation CLI Added TSLint Improved #434 #590 #513 #337 * Summary improved * Github Api Code Improvements Anonymous Gist Code Removed Initializtion in contructor github api updated proxy addition github enterprise support updated * Ignored extensions can be accidentally deleted if removeExtensions is enabled. (#604) * Fix for #516 * Update to fix `ignoredExtensions` where extensions were deleted if `removeExtensions` was enabled. * #604 * Extension summary #577 * Gist Name change #513 * #611 * fix: error translation * #611 - Changelog CLI Improvements * #611 * #611 --- .gitignore | 5 +- .travis.yml | 3 +- CHANGELOG.md | 34 +- README.md | 72 +- images/slack.png | Bin 0 -> 14163 bytes package.json | 55 +- package.nls.de.json | 95 +++ package.nls.json | 20 +- package.nls.zh-cn.json | 8 +- src/commons.ts | 1247 ++++++++++++++++++---------------- src/enums.ts | 22 +- src/environmentPath.ts | 246 ++++--- src/extension.ts | 822 ++-------------------- src/localize.ts | 45 +- src/lockfile.ts | 34 + src/service/fileService.ts | 354 +++++----- src/service/githubService.ts | 321 ++++----- src/service/pluginService.ts | 819 +++++++++++++--------- src/setting.ts | 112 ++- src/sync.ts | 927 +++++++++++++++++++++++++ src/util.ts | 333 ++++----- tsconfig.json | 25 +- tslint.json | 29 + 23 files changed, 3081 insertions(+), 2547 deletions(-) create mode 100644 images/slack.png create mode 100644 package.nls.de.json create mode 100644 src/lockfile.ts create mode 100644 src/sync.ts create mode 100644 tslint.json diff --git a/.gitignore b/.gitignore index 8a0ac487..9e98ed35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ out node_modules *.vsix -package-lock.json \ No newline at end of file +package-lock.json +*.lock +package-lock.json +*.log \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 0ee038e9..16e9a25b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,8 @@ before_install: install: - vsce package script: - - echo "npm test temporarily disabled" + - npm run tslint-check + - npm run compile deploy: provider: script diff --git a/CHANGELOG.md b/CHANGELOG.md index 074a5ca3..e30cdac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +#### Version - 3.1.0 +* Option to Install Extensions Using CLI [#434](https://github.com/shanalikhan/code-settings-sync/issues/434) + - Code Team hasn't provided API to install extension, but as CLI is available Settings Sync will start using code cli to download extensions. Live Status will be displayed while downloading. + - Note : This feature wont work in Mac as I cant test on Mac. You will be facing problems, for this I need Mac users to help and fix it. + - Fixes : [#337](https://github.com/shanalikhan/code-settings-sync/issues/337), [#566](https://github.com/shanalikhan/code-settings-sync/issues/566) and [#577](https://github.com/shanalikhan/code-settings-sync/issues/577) +* Unify the code style by using tslint and prettier - Thanks for PR [#595](https://github.com/shanalikhan/code-settings-sync/pull/595) by [@axetroy](https://github.com/axetroy) + - Fixes : [#578](https://github.com/shanalikhan/code-settings-sync/issues/578), [#597](https://github.com/shanalikhan/code-settings-sync/issues/597), [#486](https://github.com/shanalikhan/code-settings-sync/issues/486) by upgrading all the packages like Github Api. +* Don't introduce "sync.*" settings which is equal to default behavior after DL/UL - [#513](https://github.com/shanalikhan/code-settings-sync/issues/513) + - Settings Sync configuration has been changed, Readme is updated. +* Added German localization - Thanks for PR [#588](https://github.com/shanalikhan/code-settings-sync/pull/588) by [@ljosberinn](https://github.com/ljosberinn) +* Missing partial i18n translation - Thanks for PR [#593](https://github.com/shanalikhan/code-settings-sync/pull/593) by [@axetroy](https://github.com/axetroy) +* Documentation Improvement - Thanks for PR [#603](https://github.com/shanalikhan/code-settings-sync/pull/603) by [@MastaCoder](https://github.com/MastaCoder) +* Fix slack img in README and Update tutorial message - Thanks for PR [#607](https://github.com/shanalikhan/code-settings-sync/pull/607) and [#608](https://github.com/shanalikhan/code-settings-sync/pull/608) by [@fr3fou](https://github.com/fr3fou) +* Ignored extensions can be accidentally deleted if removeExtensions is enabled. - Thanks for PR [#604](https://github.com/shanalikhan/code-settings-sync/pull/604) by [@leepowellcouk](https://github.com/leepowellcouk) +* Error Translation - Thanks for PR [#616](https://github.com/shanalikhan/code-settings-sync/pull/616) by [@Xiongqi-XQ](https://github.com/Xiongqi-XQ) + #### Version - 3.0.0 * Bug Fix for OSS Variant [#549](https://github.com/shanalikhan/code-settings-sync/issues/549) - Thanks for PR [@rudfoss](https://github.com/rudfoss) @@ -6,18 +22,7 @@ * Setting to disable opening of github page [#576](https://github.com/shanalikhan/code-settings-sync/pull/576) * Update adm-zip to the latest version [#551](https://github.com/shanalikhan/code-settings-sync/pull/551) - -#### Version - 2.9.2 - -* Critical Bug Fix - Extension Download Fails [#540](https://github.com/shanalikhan/code-settings-sync/issues/540) - - -#### Version - 2.9.1 - -* Bug Fix for OSS Variant [#510](https://github.com/shanalikhan/code-settings-sync/issues/510) - Thanks for PR [@JacobHenner](https://github.com/JacobHenner) -* Readme Typo Fix [#531](https://github.com/shanalikhan/code-settings-sync/issues/531) - Thanks for PR [@x4m3](https://github.com/x4m3) -* syncLocalSettings does not support replacing setting with "False" value [#516](https://github.com/shanalikhan/code-settings-sync/issues/516) - Thanks for PR [@leepowellcouk](https://github.com/leepowellcouk) -* add support for xdg environment paths [#532](https://github.com/shanalikhan/code-settings-sync/pull/532) - Thanks for PR [@Dennor](https://github.com/Dennor) +For Previous releases change log view the [post](http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html) ## [Contributions](https://github.com/shanalikhan/code-settings-sync/blob/master/CONTRIBUTING.md) @@ -30,13 +35,12 @@ I also welcome financial contributions in case of special feature requests on my ### Community -You may join slack community and disscus the ideas over there and chat with me. +You may join slack community and disscus the ideas over there. -Drawing +Drawing I'm looking for contributors to work with me so we can make the extension smoother and more feature rich. Let me know if anyone is willing to [contribute](https://github.com/shanalikhan/code-settings-sync/blob/master/CONTRIBUTING.md). -For Previous releases change log view the [post](http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html) diff --git a/README.md b/README.md index 1552d3e8..c10b065b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Follow](https://img.shields.io/twitter/follow/itsShanKhan.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=itsShanKhan) -Drawing +Drawing @@ -22,7 +22,7 @@ 1. Use your GitHub account token and Gist. 2. Easy to Upload and Download on one click. 3. Show a summary page at the end with details about config and extensions effected. -4. Auto Download Latest Settings on Startup. +4. Auto download Latest Settings on Startup. 5. Auto upload Settings on file change. 6. Share the Gist with other users and let them download your settings. 7. Supports GitHub Enterprise @@ -36,7 +36,7 @@ All extensions and complete User Folder that Contains 2. Keybinding File 3. Launch File 4. Snippets Folder -5. VSCode Extensions Settings +5. VSCode Extensions & Extensions Configurations 6. Workspaces Folder ``` @@ -46,9 +46,6 @@ All extensions and complete User Folder that Contains 2. Download Key : Shift + Alt + D ``` - - - ## Steps To Get a Personal Access Token from GitHub This extension requires a Personal Access Token from your GitHub account. You can create one by simply following the steps shown in the pictures below. Make sure you add **Gist** in scope. @@ -172,46 +169,61 @@ Other users can give your Gist Id to download the Gist, but they can't upload th ## Settings +There are two types of settings in Settings Sync. +I will recommend you to read the configurations details [here](https://medium.com/@itsShanKhan/visual-studio-code-settings-sync-configurations-ed8dd6fd9753). -For details regarding settings keys, click [here](https://medium.com/@itsShanKhan/visual-studio-code-settings-sync-configurations-ed8dd6fd9753) +### Gist Settings + +Gist Settings are stored in `settings.json` file of Code. +You can customize the settings in gist settings like: + +``` +1. Configure Gist Id (Environment) +2. Configure auto upload / download for Github Gist +3. Configure extension sync behaviour +4. Configure force download +5. Configure quiet sync +``` ```json "sync.gist": "0c929b1a6c51015cdc9e0fe2e369ea4c", - "sync.lastUpload": "2018-03-04T14:21:39.841Z", "sync.autoDownload": false, "sync.autoUpload": false, - "sync.lastDownload": "2018-03-04T14:21:39.841Z", - "sync.forceDownload": true, - "sync.host": "", - "sync.pathPrefix": "", + "sync.forceDownload": false, "sync.quietSync": false, "sync.askGistName": false, "sync.removeExtensions": true, "sync.syncExtensions": true ``` -## Customized Sync +### Global Settings + +Global settings are present in `syncLocalSettings.json` inside `User` folder. These settings will be shared across multiple Gist Environments. + +On Windows, this is `%APPDATA%\Code\User\syncLocalSettings.json`. -Extension will create the `syncLocalSettings.json` inside `User` folder upon code start.
-On Windows, this is `%APPDATA%\Code\User\syncLocalSettings.json`.
-Mac, `$HOME/Library/Application Support/Code/User/syncLocalSettings.json`.
-Linux, `~/.config/Code/User/syncLocalSettings.json`.
+Mac, `$HOME/Library/Application Support/Code/User/syncLocalSettings.json`. + +Linux, `~/.config/Code/User/syncLocalSettings.json`. You can customize the sync: ``` 1. Options by which files / folders and settings to exclude from upload. -2. The Gist Description when creating new Gist. +2. Configure default Gist Environment name. 3. Replace the code settings after downloading. 4. Change the Gist description while creating new one in github. +5. Configure Github Enterprise Url ``` -The JSON will be created as: - ```json { - "ignoreUploadFiles": [ "projects.json", "projects_cache_vscode.json", - "projects_cache_git.json", "projects_cache_svn.json", "gpm_projects.json", + "ignoreUploadFiles": [ + "projects.json", + "projects_cache_vscode.json", + "projects_cache_git.json", + "projects_cache_svn.json", + "gpm_projects.json", "gpm-recentItems.json" ], "ignoreUploadFolders": [ @@ -221,19 +233,25 @@ The JSON will be created as: "ignored_extension_name" ], "replaceCodeSettings": { - "http.proxy": "http://my.proxy.address:8080" + "http.proxy": "http://my.proxy.address:8080" }, "gistDescription": "Visual Studio Code Settings Sync Gist", - "version": 290, + "version": 310, "token": "YOUR_GITHUB_TOKEN_HERE", "downloadPublicGist": false, "supportedFileExtensions": [ - "json", "code-snippets" - ] + "json", + "code-snippets" + ], + "openTokenLink": true, + "useCliBaseInstallation": true, + "lastUpload": null, + "lastDownload": null, + "githubEnterpriseUrl": null } ``` -For settings details, visit my post [here](https://medium.com/@itsShanKhan/visual-studio-code-settings-sync-configurations-ed8dd6fd9753) +I will recommend you to read the configurations details [here](https://medium.com/@itsShanKhan/visual-studio-code-settings-sync-configurations-ed8dd6fd9753). ## How To Contribute diff --git a/images/slack.png b/images/slack.png new file mode 100644 index 0000000000000000000000000000000000000000..725f2271057ea71a7c1ed5c62785935f3b73eb10 GIT binary patch literal 14163 zcmbVy1ymeC)8H=dA-J%4=$Q&kayIM}h9nmK$hXZNskghK-WBH|v7rVtx*DCmQ^ zrM0~%?MZVxEy&tTlvbBl6|CwgZEj_)@X6U+`;(duj;Vpm(YoAZZ6@a}X~(9~*>|hYQ3n$j-?J z7Ube#1#y8n`8mM+9Gu*2U``=0pAfeo=wBaNI5lT83n48Txqs1uUy0IML7|R9931ZM z?(FW|><-SB9GrrJf*fEj4lXV>I0T!Er#;lvgU#NB?%x<>%v~VP){anX2Yb+8jHVwP zT%n@0@R9zL1v|(8ptX1TmzdxJo_>r{##KR{~j_(S{lTxYi)1l;O_G3 zzkr*|m_p4(Y2i8(U<1Ryd^%j5LR{QJU@jIg{0#mlsH%gRwT0(@2g=PS#4GSWfx->O z%oJ++e+4#!2w6Bd+nK`IwYD?0H0N-%x1=?aKiA>asStTMQLdbX9o*w zTlftZEqN)BqO3F@ryw668y7q0KZdKSDx_%d0yVXVm@CSN(!xc9^IVbVJx$W{yw8A3KZ=0E)O{8dkKP(#@7j2R{ zX}T3n%FNaD5vj$s6gHC7Ot6fjR~XzAnkuzESgdHdxw;rGUKBvg*N>!{st{xjpvsyb$# z)0|>}0tGHRNf14v!ul%?oiLi#balyF1cWJP*jhs+yJzq@_LJ}KpG9A}?+nf9_-K2~ ziJMy@IgQst4s6Rv7c?x0=q5Se@IFG`*w*^{MGk&L=`R)#B8?3{q@tyJTz$iL-xEeA zW=O}$EVAO04yjlq=Ru~$?^R6=k|mL`rVX? zV77$omG(m^tFOw6S`7WRrcX&*6a*szKv^=nPF*5nckLY7CYouoEIwil=EW6}L&c3T0OcIpRbjL#9-d<2)S$Kdmk75!ky565d2aA z?RY^DXQWC4)xEkCq50tPK2OL<%{EIi$QLjt9uc>#z--Vfq!)7<`2^-RLV`{}fb zF0ne})`;7LGN$GCOTtJ?7X*n!doZrIn3Q6`t(Ys|SFw2gbVYyD>n0*KY>UheUW7Ef z2UZ1^oxtR0K>KlGIH1tc9s>&lw~p@V*KcdA;@_BC*y)zoWru0X4tluE_Dfud9kjxj z-tr&RD72#q_6fRXc`G}vK7FlKF~%pkFeV9^QE@CVp#6atO-rdXm*roLj`fjeVII@! zEY8xk8R7gc-6Z+X*emO^KJVd4mL2gLP0L;_RGuUl%lNY2PcvT1{y9w)7F9B?4xjw~ zis%rH?7a>NeJ2f10!0-Z14aA#&OUd+mVKq&K>x4fDFK$NxbZGV#S~_F+5-Fs9C+!6 z5LobKwV5W+t4m)BGmr}JdKW2t*v3FV2+bOmt9Qw1Ex zg@EElY39enp-GqRrE*ppSV-yrif2ielM*6rd}A%)7svlj9Plg8v~y()9<@L zLa{b2s3VsS1IMd8XzsG!MYq0Sm3Fo2j;+Fh4A`Km6rjHFK0AZTTd zseYjEglm3}V!{ELWK5|KNR<`Q!x6@VA&K`GWup;$?i3Q7nJ#<-h#5ngEJWH)yJ4&y zpc8@?r~$hccfd7}5Izyj(`_N}tW>qIU>Q}Zk%#fMEmE0{DGnNiJ@YSEa0N@4b+9^_ zSE6|HtV9fjbt7|N!cBB1DmF2H4|aN2l7y?(w@%Kob!-2TmtuwLBVVJpdoVSha~c(R zHqZPf^oclRj{5^E&kyRen;b%6I6Gahy4@+X8G@oy4PH?go@WW7S*lYN1uzNIKS(4Y zQeu2$2S_D-o>@29f%^1)u&93eO)<_A}n?rXNht+o<7%Mfz z>uMev{84cGnye26fkYsxs*pG`0GG=P*`ZrYI{tf;2lblaVe8$7GqS^*33tl#+|LsI zISsTsv)?SU6O*>`+Shv!qY&*?jE9WMJ(ZCsl?Qy+OC>v10^%Auz(Y2*7$%Lj@ze1~ zd5^;+2)gXmfNhOf^0tBaD6!v^w+&cJ3ML}!UqD|3PU(glC0%8UZZt+J zq%C0z)F+Am@?NnMMlQl~j&+JZ{Cs#qpZCwC*H zO?@LO61Vmop_F`E{DlZ9@hv(t#DJEPW)lmUp6r10tk-4?Ztzb_d`->QK9@FPI|ODH zs`V(A7$(La`5U(j<>I2%WM5G751jPsjW{u;&yG!_>}`osr_NhTceLLIYXg9UDL{ZY zt#%uDDu<`>0K2mD_x4t_%G_8RmhIbOmTh-h62-UiYn@Qbn8XYtLht4=r{4XAKWh&# zT|T5B^R<_xK>|&`UWED1AB?}v!n7X6__Nh zQ%vS?^fS)PX{kz6KFsddy)E(=3+-pf-z-&S zU4Os~SKINEj*W=S7C1X_UmdEiARzx30$wkLnzkO1r_hSJkX>j1o9#n=qK8?0-bm(b z4H1LHuW@(RY28*GnFg0zBl%qHHk5M;I#N9S=S;m2wmV79>qVj{Z!9KmhQ!V-5di># znNoNxL&E52vow-9zuf^%uLx~_fd@OySx#V5#xmf|TM3el$Z`p>< zzA9zEP*L}#kj^cZ!y_F_R4r{G+UA|AqMsE%BM&LYq|}HM6+tULOrLm(Co&RZk0W== zM{_4O9u$uqNZJRe`7rl>DM~aVBn_e5lF|_`w2g-WijfdFcpJB1yE5OI!!L@X(XE7b z64UUB`R#j-$)8@fx{Lo_9{ME~=R#6iL zMJ~q`_f)w82>9f8#ljaeLx$ge&JbhqbkjK3HNPflAN~2a ztztE$szg}O?|D7GgtPvaFNdN7R@Ssf=~5N*^ovbAdTh+}#q%Z*IBWJG=5cy@(8)~% z@#Bk2)7^nXImYvomph4iY$eQ_jw zKh#&nS_%lLdn3+GFz;}PLmk)Ckb(Mxjs+jgyRRpz3!+BQt))lHe}KR>C74|-M)El} zWjiev3sc|FC4~lpTaaV2vr>6WSzTo;4Yz(;ZvzZ&?vVRvHCJK#W8h#Tk9pcUw=T{m zC(KXwt^7}eZkL+<76)}|0ZQP#n2Ag5HnK?I$F~!yiaf04N z(8|!?UW=N(?#cjf1bffeJ3(XSD?pFchjQMpPI%dP%yvtW z6HV?6NY(x|5BUSCVkax7x$S=%?ho(loKS>a4>f*}HRqu3jl)wO8iss;_b%HhoAiHpS3VV-Nj%9FwDsH_t8AyDLOy0$jSmJ+goYo zwm(ed9gQ11xsT~72ue9Sg{Go9R~L}$qjs6~B>p_Xnse#6h}cZN`33qoRs37QcQ~TW z2&$GGG9|U>uTve@tOHYTBt+Uah^%eY2#5{St1kUd`*h+t^tErC1&W z^FK4I5!j$_eH{~dLxP!P?8sWM^atC_LTw9P^yTRKNDpJ9)GZvyw4J!$Kak6@zA=Z$eEd^UnOKAdh7%4{*w$wge#9G-S zB?b)U+s^DZ+>%Xk2+Y8z7X?1v>@9ptlco6ck9R1dM%wdOLevd}mwh8waLN-IlwY8a zgjOOaJO_#THbpv=Izpf8ID8q4LN;%Rpx)MiIrj8e(bT}h%G{$f*0hOFa-CXDcVBP-zB&0mb{!#~J4dId+}Eax zPt1YtGf$!}mlqu5p$6kX-KE5~igUG5M?5sFqd-(=mG^Y#ha?^;Pg!dE{j7v(k$7*5 z0|7nQCpk4XFDCH&X1`$r!tN-vstfX3e5><0_^qVOtA#rz+&22rDK52dSr^)z<6MLG zSAbX|=Xe+n<u09^xm&*_qx12t{M!HnF=q%Yx&Dj}>$)qmnC6 zZpA-rPV>jwTFZ4A`=F%;zJPC$Wb#ozwtEl1@eOiMQ)OKC;N4%)XKn2yehSSYHj>=$ zP~*v{1-KrSNl4{f=Jt@>ZjdGNpR6e_*rH5Jp$9%!Vsx=I#fpvXNp4kJMP9CLa)~s& z_1Iub^t#S1x1-1=*x#X!Ld?2DR$6Scto4A$nT|iGq*P>xENq-0j^T3+gstL!{Rg&U+v);jQJCryzAFZRj@@!9tfwb&yTYF( z?BDtXoWN}2I~s?E?E@IaLq~?s}KIh)X5XH2#xcV*ur98)XmwYV`5r>_?p| zoDV;BIS>-1s){qqY_@y>+1cOIZY#VpS4IOY3}|<%7EW0lKEUTawj+P0%$`Iq-%#j| z41IdK2EPy2V7eO^}-!hYI*Dp4)2nl$2P+B)M~Y3Rdat%7^ELpCFx zg1eeblMC&i^X(50@h+MRt*D=8XL7HZu7#;_-8k=tDTvzTwWr~kw7RDLdENBG+TbFsWYW0K;V{JmKk1+Nc@CQ+3`2w*=D!)IHx@*_P{~ z$b;VJ(u9~nT74d>?SCURvubGcj|vYW007?@>u7i(X$)eghsuClCUn$HZ=9a%x=$_u z31=x5C#=q|8lj7X#2|~>ZyQn)bnd5EUS-d5g*lkAZai;}j=S2#OC$SO%y0xXtWZtT zg`8MQ4P$<1t+;8wHZglcToTw7O?cGH+$HYh%aFn-^>*b032Wj}61*6k{diXaa$llY z^cz(_?#gWTi^>$Ht^t_zPTm4RE}JW)=y8lVEUFx*y~2;j0OdJA!KW3dl?y)xJ)sS|>k&097u z9HF&Ut|>T-m+;en>A9Hhy5tW(aEYznf~~c=$4eaqPahMGTX5(cN~O0u)Zp`s^tD}k z_u7eZP;l?q;OuyVqZs)l+`)Gje~^-O?B1!)J#8K2wcEgoF0vk~4_Z;f;0ao#j%*e? zWS++;LFoyok0yipO-3xj%myEjtxwj||J|WVFmL!9=bu3`9-qT5n3jU-Nl>s{z;ug6 z=VtkvJ5)B@c}R}dK;DZFMu=Fwjt??jm$R9Trq+?AMiJx7tqQI+vZQ+rg8kI|sGrP` zwvNRjTPkrLUxKh3@;Rl(`ksK<}riC>yDg>c9ij zlWg;qr(aUVW`X1Ej-_KdwC~=hQ^!)?Tu#p2?`p$Ps^t9ujd|j*LsRZpF_YJLmzryg zY5Sae!7M)>OVTw~>L$mz&sj{?2DV5%oCS5nB4&nu$!}-xDMUL+Ry6`&r<>~Q0<J0clXrOyMAF3;{9`4qj)sujPAJKK(~b1fv+9OJ`&gGd)l{nN6$0_(n|;I zACokm!*<0iy00b~U;9h!)2F{AlXxY^Jg;@MF<#>+#>JgS*ZuI2S4tPzazS>R#~|V9 z&N2A=5nc-$VXu46(rPhUF|1{CaQ#q6@1F{d=5QH{P;p4Nj7s{-Kp1{UchZ^qX%LI6Sd+|}m;4G#z< zL~F}gK{Y+HAm)*CG5CM%&<62l)o z=+lFjz}lJcIP{l%te`jJK4Ua70GG1>I8_^i7GPneGfMB|VB(8x^=igeLwuMp44jz0~c=y}ZGf@hnKCVLFN zbfV>T5<&5Orj_Hjw;)|c!;w)A+r}J?cH5Ixu8pY=Tp~-h{rD<-qt1cGYYr>Br1G(Z zI_G8vr5v|MRD*#)$M#*)NH%<$l&e3!xcHjxLK45-r&;P0wvN>ryY4Bv-5YyqY1v}& z#cAn_6)zvH-gx&0K^`iAnc&BU-!c|+_HBYC4gWnZeW4pj)YLnOQ7Pvv(X;4qPkxny z+9k4=w0k%wUeSwXe&LWJ{DCN7&@30g>HX$Qj6aDWiW9P7ejQl*ONdqEi7ZY=zd7e^ zIe9F+I$_9ax|q@yGF~Q}@lo+}b}w)4SyvyT5{=I(P;yxsnBZ`WqJ`Q$ikCRV zY(E$NvpHRJr3LXm^Ai;%zfsg-4tFeAnXqbjV!-SRdnhU5^^mB~oYST17v~-KxS~e1PG;a((W`(OvwCtv@i=K_ zgCMncxAKtFR4YZe&bg^v4rkQ?&?V<1-z@3u7y96fC`!px)^D(j(mm$#Wk7YB63>)0 zLwuzzh61AoCA$e!mzrxtRil^(o;UUG)@2yg#%1!3STy`DI4WI2rVW_^60&rNdO1+3 z)+&d5@vCm35$`eI)Vjilee4rHm;O* zU!1S6;}i-tmRcx<#h&fbNlJP&4AIDzBg#w|7(MQM^9-^ZihZ=aK2?*R!}^QFV#rgnYx*m7Ad28(rd^R}1OSMGs;4kU0EQ;6nMs1DViqCUTeIx{4zDQREHV;S)k&z8ckCv4x{JE5Ic?Ck~#us5}k0#|chR)?Ti*4=0SUbJV`SmzU3yvwRS| zA9J$49#PJTVE7(leZ3kmQ(+K#=u{14-i#JQ*kIR>w0^NdW*3SumamCbv5(Q@=Xp)V zV*6>IzQ+4F0RU^-OBlPWW%wfD&7hYhT~$ViVdB8Zm*w@k*Vsp~CL|kH_k1Z>^TkYa zn#6nG^EJY-MU8c{{Y`N~aW!IDiZQ|7tE|rM3ghr!S-z+V<tTcr@jQUK7CpIhB8Sn8cT6rY19E>|_5A6sZR zn@l5g_eltNT&`BnnbX9)y)QsYjDa%|qtXYK#OEU=CA};1S#|k3=IYdUzLynNhR*iT zE0KJ}qgew40~hp^Qi1SH;p6&M{NfuCBb$1xmuX;^Q0L*sf;eN!xMU)C&r#8lr7`L+0zteX1^Cp%fTGhoulGQWO z0ajv1GFW2wc!TM6lDd0s7=nVFx<&0{2T~yx7RM1Sx0&SpZ++21U%^A3!UG`Ec0=r% z->UJHBC`2na<}EgqQ=N2jN$Fd{1F>Nnp$d1K~G`MQxIefUXcNYmt%{ZmG;nTgXL{s z7QP~=+<0G~SL4GU7f(@*6=gEYLDdUi*097+badpYL#|_djm}-SCuR)i+-@&PJUwfZ z3MZCrxb15Nln`EhoNf&~)ZcZo^x}vXxeZYTn2avJFtzHjd2$MxA$i&$gXjNh^o)(| zM?5YEWWTDcrD;#MmAlYjcsws z)t~(AFPWCe>AvMo{cMKYOWrtEv#Ud&MwUHfo;c$mx4t!9UcnD$)z)z942(lwdy|Bc zT}8^1;e)YXpb|9rEn7K{`D8a5Pd4Y>q=R&6eu28&anTFV_!JE8L|39uSdoupYT_?l z@tI7Z2WnV7FCs~)rvUrv;^=!}b`nCL|NNp_Hx~PmdZ022**$zLs-Ec>8_nzMPNuwR z&X2I0T>#-`F5zmEeQ{nLIjdZrvs?X>H0Ls;Rn#lOxO_`_6(YZ)&SJk5ZaBMgC{A-F zmX;V;ZVMwjAw{C-$3cO=>(jM&zm<91hotPS z&iE#fy{|k^ zBR`_13oERPW2BTLlZbdaEUc2jy|Hm~L`)Z4r^1K_j$&{Xdk6f2JRW`e8Q_95F->l6 z4r(W|<6=~(?GBzo0T`nPh?j87HVgg0cw%4~%xm2dnx_L-IVMe$)y7%cY&@hzW1lLN zdx=-G`)mGjcnEpGfpoi}J)k9|EhaAp4xdo;?d!#fsp!^eG*M6mGH~#1?53ANcFamKPqoBuv&rQc8C}1-Qn78HnWP*RGfs#yGD67zU=daYSVQ= za*A{l5G+~=2GV^ey$cJhpN#Jb$CR;C(%X09lx$7KDCdpbG+I`h_3Wji=OiuJe^ zEmflRqtw{}E4TGZh&gAEJ-Tm3g3Ft8R_4Iz1-Ua=Zj^;Ch6~v*_Nr7+5@BFvR;2?N z{1Hjn3g;e-*HAWp?Ln1hZK!*cDGGWtBUKllG{-n|ysr3T$MBg;!qhuVG@MGq-z!PL z*S}v)2GYrBz;mc5KjfYrG0D+(C;Pe~16?59 zhMp?7ok=1k#i=N+P^9>9s{E-f;ROl}_}XtP7473mAF*BoZWh)!E_m$0!=`HZKJS8_5cm3#&aUvJDE`@9?fTW z#cl;TH^l*HEk%3q9F=@`lB>TPdEX!3Y?x~*S(Y#}VrkkNjf z6P{%P38B1=Tg!L3B~AS}voSe#{#Dge2&@Nx2Gi<2r+KH+xLM7yLZB? zmGQv@@)9A>Nx3HMl`YlXI_C}CYkApXd7lZf#95EKvYGng6yLD(t8mOmbq-6Smb&*3 z#r4|S29YF_)g`ci*(z0u_BUm$QmngC4_?3{`U8u!tLh!g33tXKy4jw>Pbnk8-lQKQ zA^LQwYtc-FEnuD z7BUH!Y>t_jO00)Io=DydplB>%M{i2v(!LRc1Rmxk1L=}cZz432G(SMT$(Eioz{z3J zcwgj6)1!b%h$Ui79g4FH&U;l3JzRy6FHJ8ys+c`SoN!hV zU*rhfYpTC$q3s?2N)w)_@uSbQ#p$IK0;#`s?T2l%R%yLuvXl#KTBIFGi$vTpJ!iZO zS(W*YaMy0Y7aa*GHx|IFuS1}Y6hbM_LJsAbqTr4FsHC@cb^)5sxkZ*Q+tzSapWNK$ z^J4Jm%}5+8WgAKTMG4b4K4o&-C~n4sEI4&nltz(&(ZYC>A_BGFq5k5a_t3=Ud#*N9 zuvJUn>Wd7_Qw7QZhqyFL7cC^SeOH#sHzSM5M$sjaOR$i=nyY@aX)2TS$aTI2S`1x%sT38;S;pq@fg!>9`28KM`- z4i&Y&eYxta3>?7BT12do$QuGG$B0O5P{A=x4vVd{U^wN=+{?Yp);m&jEhSYTS>13> zunsaZc%vb$kI+GMWAlm;mpZsEDdfUb*_O>C8~U+3qj+lHLhXH47Jq$&+RzH^SC8qQ z`i~~51WRv_={In{aXmz*)(-fj9+sNQ)OOHUVqowaMc>WqS3&L_3ryGNEyxF>5drsl z!ppeE)h-Pasi);8*RqEcv~-$Xq-kl{Eqbpt(r{t}Xsd;jSw#zp={_Q74cD!_D+uQd zNIfeS@mW0k`2||httgYoQ3cBzYWT3hETT_^^kX8M?~C@(PSRDyq$p8ubmLA%*Q3ce zO>;20>f6fhp@nw0v!g0%ME}1pM!#nl*zpAnwdBL_$RrQ@KLox?JYv?qGA@vK4jtA0 z0z_lLvV7=7`s|#ew3A5H@0T`sY04ZaaTPd>WHy^+=AD|8IypZy*epMNScZyjY?@={ zh{p~=%F*i)%5-E!QPVOyGULnGa+a{5Wekjs*zb(3ctc5gYkN7+WVM}VL?~UPEsk}x z0j5QGNBStyTQPk-pCPg_Z{kjD8DPu2wc&{;#v|=~7LYe#GW<-}p{Fe?VWr@n| zL}F#?RPn=XrQF>hoiC)qwS8g#A?MuyM_l zJL~reUf+9ddJ5S#miKY2b~K#&jc39b{3Y) zfRUMCm_icBNJWomtZ`tED02iR$1&?YcT5Geb7~ADxsc%sJ!*7TGeKOifD1Ech1U!S zDS^h~G zI2%0lu}4v^xPQ<0vQEc6sa#fd{n6h2!F4yVBF_+FWC`InRtVm;QI)v#j=QR_!QC5Q zSmf!XyYzXJMXoaPwT_d2=f`v^sW$(hYDc!%6)J+C_21*!p+0SXjpOL=@f>s24rJls WUMYQj`vYEf11QR>$&^c(1pE&mlwB der derzeitig eingestellte Proxy bleibt erhalten und wird anschließend überschrieben; falls leer gelassen wird der Proxy entfernt.", + "cmd.otherOptions.preserve.info.done1": "Sync : Fertig. {0} Wert wird nach Download von settings.json entfert.", + "cmd.otherOptions.preserve.info.done2": "Sync : Fertig. Erweiterung erhält nach dem Download {0} : {1} in setting.json.", + "cmd.otherOptions.joinCommunity": "Sync : Community beitreten", + "cmd.otherOptions.openIssue": "Sync : Issue erstellen", + "cmd.otherOptions.releaseNotes": "Sync : Versionshinweise", + "cmd.otherOptions.quietSync.on": "Sync : Statusleiste wird bei Up-/Download aktualisiert.", + "cmd.otherOptions.quietSync.off": "Sync : Zusammenfassung wird bei Up-/Download angezeigt.", + "cmd.otherOptions.warning.tokenNotRequire": "Sync : Settings Sync wird ab jetzt nicht mehr nach einem GitHub-Token fragen.", + "cmd.otherOptions.error.toggleFail": "Sync : Konnte Einstellung nicht ändern.", + "common.info.installed": "Sync : Einstellungen erstellt. Danke für das Benutzen dieser Erweiterung!", + "common.info.needHelp": "Sync : Benötigst du Hilfe bei der Einrichtung der Erweiterung?", + "common.info.excludeFile": "Sync : Du kannst beliebige Dateien/Ordner vom Upload und Einstellungen zum Download ausschließen.", + "common.info.updating": "Sync : Aktualisieren... bitte warten.", + "common.info.initAutoUpload": "Sync : Autoupload beginnt in 5 Sekunden.", + "common.info.setToken": "Sync : Du kannst den GitHub-Token nun manuell in `syncLocalSettings.json` definieren`", + "common.info.tokenSaved": "Sync : Token gesichert", + "common.info.gistSaved": "Sync : Gist gesichert", + "common.info.updateTo": "Sync : Auf v{0} aktualisiert", + "common.info.donate": "Sync : Gefällt dir die Erweiterung? Schon an eine Bewertung oder an eine kleine Spende gedacht? ;)", + "common.error.message": "Sync : Fehler in Konsole geloggt (Hilfe -> Entwicklerkonsole anzeigen).", + "common.error.connection": "Sync : Keine Verbindung zum Internet oder Verbindung zu GitHub nicht möglich. Fehler in Konsole geloggt.", + "common.error.canNotSave": "Sync : Kann Einstellungen nicht speicihern. Bitte settings.json validieren (z.B. unnötige Kommas)", + "common.error.invalidToken": "Sync : ungültiger / abgelaufener GitHub Token. Bitte neuen Token mit in Readme genannten Zugriffsrechten generieren. Fehler in Konsole geloggt.", + "common.error.invalidGistId": "Sync : ungültige Gist Id. Bitte verifizieren: https://gist.github.com//.", + "common.error.tokenNotSave": "Sync : Token nicht gesichert.", + "common.error.gistNotSave": "Sync : Gist nicht gesichert.", + "common.action.openExtPage": "Open Extension Page", + "common.action.openExtTutorial": "Open Tutorial", + "common.action.releaseNotes": "Versionshinweise", + "common.action.writeReview": "Bewertung schreiben", + "common.action.support": "Projekt unterstützen", + "common.action.joinCommunity": "Community beitreten", + "common.action.donate": "Spenden", + "common.placeholder.enterGithubAccessToken": "GitHub Personal Access Token eingeben", + "common.placeholder.enterGistId": "Gist-ID eingeben", + "common.placeholder.multipleGist": "Gist-Name [ z.B. Persönliche Einstellungen ]", + "common.prompt.multipleGist": "Ermöglicht dir im Falle mehrerer Gists die Einstellungen zu identizifieren.", + "common.prompt.enterGistId": + "Gist-ID der vorherig hochgeladeenen Einstellungen eingeben. Du kannst sie außerdem manuell in den Einstellungen setzen (sync.gist). [Enter] um fortzufahren, [Escape] um abzubrechen).", + "common.prompt.enterGithubAccessToken": "Du kannst den Token auch manuell hinzufügen (Benutzerordner/syncLocalSettings.json). [Enter] um fortzufahren, [Escape] um abzurechen." +} \ No newline at end of file diff --git a/package.nls.json b/package.nls.json index d7b9a2d1..bc89355d 100644 --- a/package.nls.json +++ b/package.nls.json @@ -7,7 +7,7 @@ "ext.config.autoUpload": "Set it true to Auto Upload on the settings change. [Code Restart Required]", "ext.config.forceDownload": "Set it to true if you want to download the settings even when you have latest settings.", "ext.config.host": "Set it to your GitHub Enterprise host if you want to use GHE.", - "ext.config.pathPrefix": "Set it to your GitHub Enterprise api prefix if you want to use GHE. Normally '/api/v3'. Only used when host is set.", + "ext.config.pathPrefix": "Set it to your GitHub Enterprise API prefix if you want to use GHE. Normally '/api/v3'. Only used when host is set.", "ext.config.quietSync": "When set to true, will show the result in status bar instead of summary page.", "ext.config.askGistName": "Ask gist name upon creating. Helps you to identify the gist if you have multiple gists.", "ext.config.removeExtensions": "Set it to false if you dont want to remove extensions while downloading.", @@ -26,16 +26,18 @@ "cmd.updateSettings.error.readGistFail": "Sync : GIST ID: {0} UNABLE TO READ.", "cmd.updateSettings.error.gistNotSave": "Sync : GIST NOT SAVED", "cmd.downloadSettings.title": "Sync : Download Settings", + "cmd.downloadSettings.info.downloaded": "Sync : Download Complete.", "cmd.downloadSettings.info.readdingOnline": "Sync : Reading Settings Online.", "cmd.downloadSettings.info.gotLatestVersion": "Sync : You already have latest version of saved settings.", "cmd.downloadSettings.error.removeExtFail": "Sync : Unable to remove some extensions.", + "cmd.downloadSettings.error.unableSave": "Sync : Unable to save extension settings file.", "cmd.resetSettings.title": "Sync : Reset Extension Settings", "cmd.resetSettings.info.resetting": "Sync : Resetting Your Settings.", "cmd.resetSettings.info.settingClear": "Sync : Settings Cleared.", "cmd.otherOptions.title": "Sync : Advanced Options", "cmd.otherOptions.editLocalSetting": "Sync : Edit Extension Local Settings", "cmd.otherOptions.shareSetting": "Sync : Share Settings with Public GIST", - "cmd.otherOptions.shareSetting.beforeConfirm": "Sync : This will remove current GIST and upload settings on new public GIST. Do you want to continue ?", + "cmd.otherOptions.shareSetting.beforeConfirm": "Sync : This will remove current GIST and upload settings on new public GIST. Do you want to continue?", "cmd.otherOptions.downloadSetting": "Sync : Download Settings from Public GIST", "cmd.otherOptions.toggleForceDownload": "Sync : Toggle Force Download", "cmd.otherOptions.toggleForceDownload.on": "Sync : Force Download Turned On.", @@ -59,8 +61,8 @@ "cmd.otherOptions.quietSync.off": "Sync : Summary will be shown upon download / upload.", "cmd.otherOptions.warning.tokenNotRequire": "Sync : Settings Sync will not ask for GitHub Token from now on.", "cmd.otherOptions.error.toggleFail": "Sync : Unable to Toggle.", - "common.info.installed": "Sync : Settings Created. Thank You for Installing !", - "common.info.needHelp": "Sync : Need Help regarding configuring this extension ?", + "common.info.installed": "Sync : Settings created, thank you for installing!", + "common.info.needHelp": "Sync : Need Help configuring this extension?", "common.info.excludeFile": "Sync : You can exclude any file / folder for upload and settings for download.", "common.info.updating": "Sync : Updating In Progress ... Please Wait.", "common.info.initAutoUpload": "Sync : Auto Upload Initiating In 5 Seconds.", @@ -68,7 +70,7 @@ "common.info.tokenSaved": "Sync : Token Saved", "common.info.gistSaved": "Sync : Gist Saved", "common.info.updateTo": "Sync : Updated to v{0}", - "common.info.donate": "Sync : Do you like this extension ? How about writing a review or send me some donation ;)", + "common.info.donate": "Sync : Do you like this extension? How about writing a review or sending a donation? ;)", "common.error.message": "Sync : Error Logged In Console (Help menu > Toggle Developer Tools).", "common.error.connection": "Sync : Internet Not Connected or Unable to Connect to GitHub. Exception Logged in Console", "common.error.canNotSave": "Sync : Unable to Save Settings. Please make sure you have valid JSON settings.json file. ( e.g : No trailing commas )", @@ -85,8 +87,8 @@ "common.action.donate": "Donate Now", "common.placeholder.enterGithubAccessToken": "Enter GitHub Personal Access Token", "common.placeholder.enterGistId": "Enter Gist Id", - "common.placeholder.multipleGist": "Gist Name [ e.g : Personal Settings ]", + "common.placeholder.multipleGist": "Gist Name (e.g : Personal Settings)", "common.prompt.multipleGist": "Allows you to identify the settings if you have multiple gist.", - "common.prompt.enterGistId": "Enter Gist Id from previously uploaded settings. You can also set manually in code settings (sync.gist). Press [Enter] or press / type 'esc' to cancel.", - "common.prompt.enterGithubAccessToken": "You can manually add token also (User Folder / syncLocalSettings.json). Press [Enter] or press / type 'esc' to cancel." -} \ No newline at end of file + "common.prompt.enterGistId": "Enter Gist Id from previously uploaded settings. You can also set manually in code settings (sync.gist). Press [Enter] or [Esc] to cancel.", + "common.prompt.enterGithubAccessToken": "You can manually add token also (User Folder / syncLocalSettings.json). Press [Enter] or [Esc] to cancel." +} diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 97f05bba..a020644f 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -10,7 +10,7 @@ "ext.config.pathPrefix": "如果你是 Github 企业用户, 设置 Github 的 API 前缀. 通常是 '/api/v3'. 只在 host 字段设置后生效", "ext.config.quietSync": "如果设置为 true, 开启静默模式. 上传和下载的概要信息显示在状态栏而非输出面板", "ext.config.askGistName": "在创建时询问 gist 的名称. 能够帮助你识别多个 gist.", - "ext.config.removeExtensions": "设置为 false 如果你想在下载时移除扩展.", + "ext.config.removeExtensions": "设置为 false 如果你不想在下载时移除扩展.", "ext.config.syncExtensions": "设置为 false 如果你不想上传/下载扩展.", "cmd.howSetting.title": "Sync : 如何配置", "cmd.updateSettings.title": "Sync : 上传设置", @@ -25,10 +25,12 @@ "cmd.updateSettings.error.newGistCreateFail": "Sync : 无法创建 Gist", "cmd.updateSettings.error.readGistFail": "Sync : 不能读取 ID 为 {0} 的 Gist", "cmd.updateSettings.error.gistNotSave": "Sync : Gist 未保存", - "cmd.downloadSettings.title": "Sync : 下载设置", + "cmd.downloadSettings.title": "Sync : 下载设置", + "cmd.downloadSettings.info.downloaded": "Sync : 下载完成", "cmd.downloadSettings.info.readdingOnline": "Sync : 正在读取远程设置", "cmd.downloadSettings.info.gotLatestVersion": "Sync : 现在您已使用最新版本的设置.", "cmd.downloadSettings.error.removeExtFail": "Sync : 不能移除一些扩展", + "cmd.downloadSettings.error.unableSave": "Sync : 无法保存扩展配置文件", "cmd.resetSettings.title": "Sync : 重置设置", "cmd.resetSettings.info.resetting": "Sync : 正在重置你的设置", "cmd.resetSettings.info.settingClear": "Sync : 设置清除完毕", @@ -89,4 +91,4 @@ "common.prompt.multipleGist": "如果你有多个 Gist 设置, 请输入名称扩展才得以识别", "common.prompt.enterGistId": "请输入上一次上传设置的 Gist Id. 你也可以手动添加到 VSCode 的配置文件 (sync.gist 字段). 按下 [Enter] 确认或者 输入 'esc' 取消", "common.prompt.enterGithubAccessToken": "链接打开了! 你可以手动添加到 用户目录/syncLocalSettings.json. 按下 [Enter] 确认或者 输入 'esc' 取消" -} \ No newline at end of file +} diff --git a/src/commons.ts b/src/commons.ts index 05443104..88662ffa 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -1,634 +1,715 @@ "use strict"; -import * as fs from 'fs'; -import * as path from 'path'; -import * as vscode from 'vscode'; +import * as chokidar from "chokidar"; +import * as fs from "fs-extra"; +import * as vscode from "vscode"; +import { Environment } from "./environmentPath"; +import localize from "./localize"; +import * as lockfile from "./lockfile"; +import { File, FileService } from "./service/fileService"; +import { ExtensionInformation } from "./service/pluginService"; +import { CustomSettings, ExtensionConfig, LocalConfig } from "./setting"; +import { Util } from "./util"; -import { Environment } from './environmentPath'; -import localize from './localize'; -import { File, FileService } from './service/fileService'; -import { ExtensionInformation } from './service/pluginService'; -import { CustomSettings, ExtensionConfig, LocalConfig } from './setting'; +export default class Commons { + public static LogException( + error: any, + message: string, + msgBox: boolean, + callback?: () => void + ): void { + if (error) { + console.error(error); + if (error.code === 500) { + message = localize("common.error.connection"); + msgBox = false; + } else if (error.code === 4) { + message = localize("common.error.canNotSave"); + } else if (error.message) { + try { + message = JSON.parse(error.message).message; + if (message.toLowerCase() === "bad credentials") { + msgBox = true; + message = localize("common.error.invalidToken"); + // vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/settings/tokens')); + } + if (message.toLowerCase() === "not found") { + msgBox = true; + message = localize("common.error.invalidGistId"); + } + } catch (error) { + // message = error.message; + } + } + } -const chokidar = require('chokidar'); -const lockfile = require('proper-lockfile'); + if (msgBox === true) { + vscode.window.showErrorMessage(message); + vscode.window.setStatusBarMessage("").dispose(); + } else { + vscode.window.setStatusBarMessage(message, 5000); + } -export default class Commons { + if (callback) { + callback.apply(this); + } + } + + public static GetInputBox(token: boolean) { + if (token) { + const options: vscode.InputBoxOptions = { + placeHolder: localize("common.placeholder.enterGithubAccessToken"), + password: false, + prompt: localize("common.prompt.enterGithubAccessToken"), + ignoreFocusOut: true + }; + return options; + } else { + const options: vscode.InputBoxOptions = { + placeHolder: localize("common.placeholder.enterGistId"), + password: false, + prompt: localize("common.prompt.enterGistId"), + ignoreFocusOut: true + }; + return options; + } + } - public ERROR_MESSAGE: string = localize("common.error.message"); - private static configWatcher = null; - private static extensionWatcher = null; - private static outputChannel: vscode.OutputChannel = null; + private static configWatcher = null; + private static extensionWatcher = null; + private static outputChannel: vscode.OutputChannel = null; - constructor(private en: Environment, private context: vscode.ExtensionContext) { + public ERROR_MESSAGE: string = localize("common.error.message"); + constructor( + private en: Environment, + private context: vscode.ExtensionContext + ) {} + + public async StartWatch(): Promise { + const lockExist: boolean = await FileService.FileExists( + this.en.FILE_SYNC_LOCK + ); + if (!lockExist) { + fs.closeSync(fs.openSync(this.en.FILE_SYNC_LOCK, "w")); } - public static LogException(error: any, message: string, msgBox: boolean, callback?: Function): void { + // check is sync locking + if (await lockfile.Check(this.en.FILE_SYNC_LOCK)) { + await lockfile.Unlock(this.en.FILE_SYNC_LOCK); + } - if (error) { - console.error(error); - if (error.code == 500) { - message = localize("common.error.connection"); - msgBox = false; - } - else if (error.code == 4) { - message = localize("common.error.canNotSave"); - } - else if (error.message) { - try { - message = JSON.parse(error.message).message; - if (message.toLowerCase() == 'bad credentials') { - msgBox = true; - message = localize("common.error.invalidToken"); - //vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/settings/tokens')); - } - if (message.toLowerCase() == 'not found') { - msgBox = true; - message = localize("common.error.invalidGistId") - } - } catch (error) { - //message = error.message; - } + let uploadStopped: boolean = true; + Commons.extensionWatcher = chokidar.watch(this.en.ExtensionFolder, { + depth: 0, + ignoreInitial: true + }); + Commons.configWatcher = chokidar.watch(this.en.PATH + "/User/", { + depth: 2, + ignoreInitial: true + }); + + // TODO : Uncomment the following lines when code allows feature to update Issue in github code repo - #14444 + + // Commons.extensionWatcher.on('addDir', (path, stat)=> { + // if (uploadStopped) { + // uploadStopped = false; + // this.InitiateAutoUpload().then((resolve) => { + // uploadStopped = resolve; + // }, (reject) => { + // uploadStopped = reject; + // }); + // } + // else { + // vscode.window.setStatusBarMessage(""); + // vscode.window.setStatusBarMessage("Sync : Updating In Progres... Please Wait.", 3000); + // } + // }); + // Commons.extensionWatcher.on('unlinkDir', (path)=> { + // if (uploadStopped) { + // uploadStopped = false; + // this.InitiateAutoUpload().then((resolve) => { + // uploadStopped = resolve; + // }, (reject) => { + // uploadStopped = reject; + // }); + // } + // else { + // vscode.window.setStatusBarMessage(""); + // vscode.window.setStatusBarMessage("Sync : Updating In Progres... Please Wait.", 3000); + // } + // }); + + Commons.configWatcher.on("change", async (path: string) => { + // check sync is locking + if (await lockfile.Check(this.en.FILE_SYNC_LOCK)) { + uploadStopped = false; + } + + if (!uploadStopped) { + vscode.window.setStatusBarMessage("").dispose(); + vscode.window.setStatusBarMessage( + localize("common.info.updating"), + 3000 + ); + return false; + } + + uploadStopped = false; + await lockfile.Lock(this.en.FILE_SYNC_LOCK); + const settings: ExtensionConfig = this.GetSettings(); + const customSettings: CustomSettings = await this.GetCustomSettings(); + if (customSettings == null) { + return; + } + + let requiredFileChanged: boolean = false; + if ( + customSettings.ignoreUploadFolders.indexOf("workspaceStorage") === -1 + ) { + requiredFileChanged = + path.indexOf(this.en.FILE_SYNC_LOCK_NAME) === -1 && + path.indexOf(".DS_Store") === -1 && + path.indexOf(this.en.APP_SUMMARY_NAME) === -1 && + path.indexOf(this.en.FILE_CUSTOMIZEDSETTINGS_NAME) === -1; + } else { + requiredFileChanged = + path.indexOf(this.en.FILE_SYNC_LOCK_NAME) === -1 && + path.indexOf("workspaceStorage") === -1 && + path.indexOf(".DS_Store") === -1 && + path.indexOf(this.en.APP_SUMMARY_NAME) === -1 && + path.indexOf(this.en.FILE_CUSTOMIZEDSETTINGS_NAME) === -1; + } + + console.log("Sync : File Change Detected On : " + path); + + if (requiredFileChanged) { + if (settings.autoUpload) { + if ( + customSettings.ignoreUploadFolders.indexOf("workspaceStorage") > -1 + ) { + const fileType: string = path.substring( + path.lastIndexOf("."), + path.length + ); + if (fileType.indexOf("json") === -1) { + console.log( + "Sync : Cannot Initiate Auto-upload on This File (Not JSON)." + ); + uploadStopped = true; + return; } + } + + console.log("Sync : Initiating Auto-upload For File : " + path); + this.InitiateAutoUpload(path) + .then(isDone => { + uploadStopped = isDone; + return lockfile.Unlock(this.en.FILE_SYNC_LOCK); + }) + .catch(() => { + uploadStopped = true; + return lockfile.Unlock(this.en.FILE_SYNC_LOCK); + }); } - - if (msgBox == true) { - vscode.window.showErrorMessage(message); - vscode.window.setStatusBarMessage("").dispose(); + } else { + uploadStopped = true; + await lockfile.Unlock(this.en.FILE_SYNC_LOCK); + } + }); + } + + public async InitiateAutoUpload(path: string): Promise { + vscode.window.setStatusBarMessage("").dispose(); + vscode.window.setStatusBarMessage( + localize("common.info.initAutoUpload"), + 5000 + ); + + await Util.Sleep(3000); + + vscode.commands.executeCommand( + "extension.updateSettings", + "forceUpdate", + path + ); + + return true; + } + + public CloseWatch(): void { + if (Commons.configWatcher != null) { + Commons.configWatcher.close(); + } + if (Commons.extensionWatcher != null) { + Commons.extensionWatcher.close(); + } + } + + public async InitalizeSettings( + askToken: boolean, + askGist: boolean + ): Promise { + const settings: LocalConfig = new LocalConfig(); + const extSettings: ExtensionConfig = this.GetSettings(); + const cusSettings: CustomSettings = await this.GetCustomSettings(); + + if (cusSettings.token === "") { + if (askToken === true) { + askToken = !cusSettings.downloadPublicGist; + } + + if (askToken) { + if (cusSettings.openTokenLink) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse("https://github.com/settings/tokens") + ); } - else { - vscode.window.setStatusBarMessage(message, 5000); + const tokTemp: string = await this.GetTokenAndSave(cusSettings); + if (!tokTemp) { + const msg = localize("common.error.tokenNotSave"); + vscode.window.showErrorMessage(msg); + throw new Error(msg); } + cusSettings.token = tokTemp; + } + } - if (callback) { - callback.apply(this); + if (extSettings.gist === "") { + if (askGist) { + const gistTemp: string = await this.GetGistAndSave(extSettings); + if (!gistTemp) { + const msg = localize("common.error.gistNotSave"); + vscode.window.showErrorMessage(msg); + throw new Error(msg); } + extSettings.gist = gistTemp; + } } - - public async StartWatch(): Promise { - - let lockExist: boolean = await FileService.FileExists(this.en.FILE_SYNC_LOCK); - if (!lockExist) { - fs.closeSync(fs.openSync(this.en.FILE_SYNC_LOCK, 'w')); + settings.customConfig = cusSettings; + settings.extConfig = extSettings; + return settings; + } + + public async GetCustomSettings(): Promise { + let customSettings: CustomSettings = new CustomSettings(); + try { + const customExist: boolean = await FileService.FileExists( + this.en.FILE_CUSTOMIZEDSETTINGS + ); + if (customExist) { + const customSettingStr: string = await FileService.ReadFile( + this.en.FILE_CUSTOMIZEDSETTINGS + ); + const tempObj: { + [key: string]: any; + ignoreUploadSettings: string[]; + } = JSON.parse(customSettingStr); + if (!Array.isArray(tempObj.ignoreUploadSettings)) { + tempObj.ignoreUploadSettings = []; } - - let self: Commons = this; - let locked: boolean = lockfile.checkSync(this.en.FILE_SYNC_LOCK); - if (locked) { - lockfile.unlockSync(this.en.FILE_SYNC_LOCK); + Object.assign(customSettings, tempObj); + customSettings.token = customSettings.token.trim(); + return customSettings; + } + } catch (e) { + Commons.LogException( + e, + "Sync : Unable to read " + + this.en.FILE_CUSTOMIZEDSETTINGS_NAME + + ". Make sure its Valid JSON.", + true + ); + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "http://shanalikhan.github.io/2017/02/19/Option-to-ignore-settings-folders-code-settings-sync.html" + ) + ); + customSettings = null; + return customSettings; + } + } + + public async SetCustomSettings(setting: CustomSettings): Promise { + try { + const json: { [key: string]: any; ignoreUploadSettings: string[] } = { + ...setting + }; + delete json.ignoreUploadSettings; + await FileService.WriteFile( + this.en.FILE_CUSTOMIZEDSETTINGS, + JSON.stringify(json) + ); + return true; + } catch (e) { + Commons.LogException( + e, + "Sync : Unable to write " + this.en.FILE_CUSTOMIZEDSETTINGS_NAME, + true + ); + return false; + } + } + + public async StartMigrationProcess(): Promise { + const fileExist: boolean = await FileService.FileExists( + this.en.FILE_CUSTOMIZEDSETTINGS + ); + let customSettings: CustomSettings = null; + const firstTime: boolean = !fileExist; + let fileChanged: boolean = firstTime; + + if (fileExist) { + customSettings = await this.GetCustomSettings(); + } else { + customSettings = new CustomSettings(); + } + // vscode.workspace.getConfiguration().update("sync.version", undefined, true); + + if (firstTime) { + const openExtensionPage = localize("common.action.openExtPage"); + const openExtensionTutorial = localize("common.action.openExtTutorial"); + vscode.window.showInformationMessage(localize("common.info.installed")); + vscode.window + .showInformationMessage( + localize("common.info.needHelp"), + openExtensionPage + ) + .then((val: string) => { + if (val === openExtensionPage) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" + ) + ); + } + }); + vscode.window + .showInformationMessage( + localize("common.info.excludeFile"), + openExtensionTutorial + ) + .then((val: string) => { + if (val === openExtensionTutorial) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://shanalikhan.github.io/2016/07/31/Visual-Studio-code-sync-setting-edit-manually.html" + ) + ); + } + }); + } else if (customSettings.version < Environment.CURRENT_VERSION) { + fileChanged = true; + if (this.context.globalState.get("synctoken")) { + const token = this.context.globalState.get("synctoken"); + if (token !== "") { + customSettings.token = String(token); + this.context.globalState.update("synctoken", ""); + vscode.window.showInformationMessage( + localize("common.info.setToken") + ); } - let uploadStopped: boolean = true; - Commons.extensionWatcher = chokidar.watch(this.en.ExtensionFolder, { depth: 0, ignoreInitial: true }); - Commons.configWatcher = chokidar.watch(this.en.PATH + "/User/", { depth: 2, ignoreInitial: true }); - - //TODO : Uncomment the following lines when code allows feature to update Issue in github code repo - #14444 - - // Commons.extensionWatcher.on('addDir', (path, stat)=> { - // if (uploadStopped) { - // uploadStopped = false; - // this.InitiateAutoUpload().then((resolve) => { - // uploadStopped = resolve; - // }, (reject) => { - // uploadStopped = reject; - // }); - // } - // else { - // vscode.window.setStatusBarMessage(""); - // vscode.window.setStatusBarMessage("Sync : Updating In Progres... Please Wait.", 3000); - // } - // }); - // Commons.extensionWatcher.on('unlinkDir', (path)=> { - // if (uploadStopped) { - // uploadStopped = false; - // this.InitiateAutoUpload().then((resolve) => { - // uploadStopped = resolve; - // }, (reject) => { - // uploadStopped = reject; - // }); - // } - // else { - // vscode.window.setStatusBarMessage(""); - // vscode.window.setStatusBarMessage("Sync : Updating In Progres... Please Wait.", 3000); - // } - // }); - - Commons.configWatcher.on('change', async (path: string) => { - let locked: boolean = lockfile.checkSync(this.en.FILE_SYNC_LOCK); - if (locked) { - uploadStopped = false; - } - - if (uploadStopped) { - uploadStopped = false; - lockfile.lockSync(self.en.FILE_SYNC_LOCK); - let settings: ExtensionConfig = this.GetSettings(); - let customSettings: CustomSettings = await this.GetCustomSettings(); - if (customSettings == null) { - return; - } - - let requiredFileChanged: boolean = false; - if (customSettings.ignoreUploadFolders.indexOf("workspaceStorage") == -1) { - requiredFileChanged = (path.indexOf(self.en.FILE_SYNC_LOCK_NAME) == -1) && (path.indexOf(".DS_Store") == -1) && (path.indexOf(this.en.APP_SUMMARY_NAME) == -1) && (path.indexOf(this.en.FILE_CUSTOMIZEDSETTINGS_NAME) == -1); - } - else { - requiredFileChanged = (path.indexOf(self.en.FILE_SYNC_LOCK_NAME) == -1) && (path.indexOf("workspaceStorage") == -1) && (path.indexOf(".DS_Store") == -1) && (path.indexOf(this.en.APP_SUMMARY_NAME) == -1) && (path.indexOf(this.en.FILE_CUSTOMIZEDSETTINGS_NAME) == -1); - } - - console.log("Sync : File Change Detected On : " + path); - - if (requiredFileChanged) { - if (settings.autoUpload) { - if (customSettings.ignoreUploadFolders.indexOf("workspaceStorage") > -1) { - let fileType: string = path.substring(path.lastIndexOf('.'), path.length); - if (fileType.indexOf('json') == -1) { - console.log("Sync : Cannot Initiate Auto-upload on This File (Not JSON)."); - uploadStopped = true; - return; - } - } - - console.log("Sync : Initiating Auto-upload For File : " + path); - this.InitiateAutoUpload(path).then((resolve) => { - uploadStopped = resolve; - lockfile.unlockSync(self.en.FILE_SYNC_LOCK); - }, (reject) => { - lockfile.unlockSync(self.en.FILE_SYNC_LOCK); - uploadStopped = true; - }); - } - } else { - uploadStopped = true; - lockfile.unlockSync(self.en.FILE_SYNC_LOCK); - } - } - else { - vscode.window.setStatusBarMessage("").dispose(); - vscode.window.setStatusBarMessage(localize("common.info.updating"), 3000); - } + } + + const releaseNotes = localize("common.action.releaseNotes"); + const writeReview = localize("common.action.writeReview"); + const support = localize("common.action.support"); + const joinCommunity = localize("common.action.joinCommunity"); + // TODO : Remove this, v3.1 Specific only. + vscode.window.showInformationMessage( + "Some Settings are updated. You can remove unnecessary sync settings from code. Read Sync guide for details." + ); + vscode.window + .showInformationMessage( + localize("common.info.updateTo", Environment.getVersion()), + releaseNotes, + writeReview, + support, + joinCommunity + ) + .then((val: string) => { + if (val === releaseNotes) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html" + ) + ); + } + if (val === writeReview) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync#review-details" + ) + ); + } + if (val === support) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=4W3EWHHBSYMM8&lc=IE&item_name=Code%20Settings%20Sync&item_number=visual%20studio%20code%20settings%20sync¤cy_code=USD&bn=PP-DonationsBF:btn_donate_SM.gif:NonHosted" + ) + ); + } + if (val === joinCommunity) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk" + ) + ); + } }); } - - public async InitiateAutoUpload(path: string): Promise { - - return new Promise(async (resolve, reject) => { - vscode.window.setStatusBarMessage("").dispose(); - vscode.window.setStatusBarMessage(localize("common.info.initAutoUpload"), 5000); - - setTimeout(function () { - vscode.commands.executeCommand('extension.updateSettings', "forceUpdate", path).then((res) => { - resolve(true); - }); - }, 3000); - }); + if (fileChanged) { + customSettings.version = Environment.CURRENT_VERSION; + await this.SetCustomSettings(customSettings); } - - public CloseWatch(): void { - if (Commons.configWatcher != null) { - Commons.configWatcher.close(); - } - if (Commons.extensionWatcher != null) { - Commons.extensionWatcher.close(); + return true; + } + + public async SaveSettings(setting: ExtensionConfig): Promise { + const config = vscode.workspace.getConfiguration("sync"); + const allKeysUpdated = new Array>(); + + const keys = Object.keys(setting); + keys.forEach(async keyName => { + if (setting[keyName] == null) { + setting[keyName] = ""; + } + if (keyName.toLowerCase() !== "token") { + allKeysUpdated.push(config.update(keyName, setting[keyName], true)); + } + }); + + try { + await Promise.all(allKeysUpdated); + if (this.context.globalState.get("syncCounter")) { + const counter = this.context.globalState.get("syncCounter"); + let count: number = parseInt(counter + "", 10); + if (count % 450 === 0) { + this.DonateMessage(); } + count = count + 1; + this.context.globalState.update("syncCounter", count); + } else { + this.context.globalState.update("syncCounter", 1); + } + return true; + } catch (err) { + Commons.LogException(err, this.ERROR_MESSAGE, true); + return false; } - - public async InitalizeSettings(askToken: boolean, askGist: boolean): Promise { - let me: Commons = this; - return new Promise(async (resolve, reject) => { - var settings: LocalConfig = new LocalConfig(); - var extSettings: ExtensionConfig = me.GetSettings() - var cusSettings: CustomSettings = await me.GetCustomSettings(); - - if (cusSettings.token == "") { - if (askToken == true) { - askToken = !cusSettings.downloadPublicGist; - } - - if (askToken) { - if (cusSettings.openTokenLink) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/settings/tokens')); - } - let tokTemp: string = await me.GetTokenAndSave(cusSettings); - if (!tokTemp) { - vscode.window.showErrorMessage(localize("common.error.tokenNotSave")); - reject(false); - } - cusSettings.token = tokTemp; - } - } - - - if (extSettings.gist == "") { - if (askGist) { - let gistTemp: string = await me.GetGistAndSave(extSettings); - if (!gistTemp) { - vscode.window.showErrorMessage(localize("common.error.gistNotSave")); - reject(false); - } - extSettings.gist = gistTemp; - } - } - settings.customConfig = cusSettings; - settings.extConfig = extSettings; - resolve(settings); - }); + } + + public async DonateMessage(): Promise { + const donateNow = localize("common.action.donate"); + const writeReview = localize("common.action.writeReview"); + const res = await vscode.window.showInformationMessage( + localize("common.info.donate"), + donateNow, + writeReview + ); + + if (res === donateNow) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=4W3EWHHBSYMM8&lc=IE&item_name=Code%20Settings%20Sync&item_number=visual%20studio%20code%20settings%20sync¤cy_code=USD&bn=PP-DonationsBF:btn_donate_SM.gif:NonHosted" + ) + ); + } else if (res === writeReview) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync#review-details" + ) + ); } + } - public async GetCustomSettings(): Promise { - let me: Commons = this; - return new Promise(async (resolve, reject) => { - - let customSettings: CustomSettings = new CustomSettings(); - try { - let customExist: boolean = await FileService.FileExists(me.en.FILE_CUSTOMIZEDSETTINGS); - if (customExist) { - let customSettingStr: string = await FileService.ReadFile(me.en.FILE_CUSTOMIZEDSETTINGS); - let tempObj: Object = JSON.parse(customSettingStr); - if (!Array.isArray(tempObj["ignoreUploadSettings"])) { - tempObj["ignoreUploadSettings"] = new Array(); - } - Object.assign(customSettings, tempObj); - customSettings.token = customSettings.token.trim(); - resolve(customSettings); - } - } - catch (e) { - Commons.LogException(e, "Sync : Unable to read " + this.en.FILE_CUSTOMIZEDSETTINGS_NAME + ". Make sure its Valid JSON.", true); - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('http://shanalikhan.github.io/2017/02/19/Option-to-ignore-settings-folders-code-settings-sync.html')); - customSettings = null; - resolve(customSettings); - } - }); - } + public GetSettings(): ExtensionConfig { + const settings = new ExtensionConfig(); - public async SetCustomSettings(setting: CustomSettings): Promise { - let me: Commons = this; - return new Promise(async (resolve, reject) => { - try { - let json: Object = Object.assign(setting); - delete json["ignoreUploadSettings"] - await FileService.WriteFile(me.en.FILE_CUSTOMIZEDSETTINGS, JSON.stringify(json)); - resolve(true); - } - catch (e) { - Commons.LogException(e, "Sync : Unable to write " + this.en.FILE_CUSTOMIZEDSETTINGS_NAME, true); - resolve(false); - } - }); + for (const key of Object.keys(settings)) { + if (key !== "token") { + settings[key] = vscode.workspace.getConfiguration("sync").get(key); + } } - public StartMigrationProcess(): Promise { - let me: Commons = this; - let settingKeys = Object.keys(new ExtensionConfig()); - return new Promise(async (resolve, reject) => { - - let settings: ExtensionConfig = await me.GetSettings(); - let fileExist: boolean = await FileService.FileExists(me.en.FILE_CUSTOMIZEDSETTINGS); - let customSettings: CustomSettings = null; - let firstTime: boolean = !fileExist; - let fileChanged: boolean = firstTime; + settings.gist = settings.gist.trim(); + return settings; + } - if (fileExist) { - customSettings = await me.GetCustomSettings(); - } - else { - customSettings = new CustomSettings(); - } - //vscode.workspace.getConfiguration().update("sync.version", undefined, true); - - if (firstTime) { - const openExtensionPage = localize("common.action.openExtPage"); - const openExtensionTutorial = localize("common.action.openExtTutorial"); - vscode.window.showInformationMessage(localize("common.info.installed")); - vscode.window.showInformationMessage(localize("common.info.needHelp"), openExtensionPage).then(function (val: string) { - if (val == openExtensionPage) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync')) - } - }); - vscode.window.showInformationMessage(localize("common.info.excludeFile"), openExtensionTutorial).then(function (val: string) { - if (val == openExtensionTutorial) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('http://shanalikhan.github.io/2017/02/19/Option-to-ignore-settings-folders-code-settings-sync.html')) - } - }); - } - else if (customSettings.version < Environment.CURRENT_VERSION) { - fileChanged = true; - if (this.context.globalState.get('synctoken')) { - let token = this.context.globalState.get('synctoken'); - if (token != "") { - customSettings.token = String(token); - this.context.globalState.update("synctoken", ""); - vscode.window.showInformationMessage(localize("common.info.setToken")); - } - } - - const releaseNotes = localize("common.action.releaseNotes"); - const writeReview = localize("common.action.writeReview"); - const support = localize("common.action.support"); - const joinCommunity = localize("common.action.joinCommunity"); - - vscode.window.showInformationMessage(localize("common.info.updateTo", Environment.getVersion()), releaseNotes, writeReview, support, joinCommunity).then(function (val: string) { - if (val == releaseNotes) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html')); - } - if (val == writeReview) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync#review-details')); - } - if (val == support) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=4W3EWHHBSYMM8&lc=IE&item_name=Code%20Settings%20Sync&item_number=visual%20studio%20code%20settings%20sync¤cy_code=USD&bn=PP-DonationsBF:btn_donate_SM.gif:NonHosted')); - } - if (val == joinCommunity) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk')); - } - }); - } - if (fileChanged) { - customSettings.version = Environment.CURRENT_VERSION; - await me.SetCustomSettings(customSettings); - } - resolve(true); - }); - } + public async GetTokenAndSave(sett: CustomSettings): Promise { + const opt = Commons.GetInputBox(true); - public async SaveSettings(setting: ExtensionConfig): Promise { - let me: Commons = this; - let config = vscode.workspace.getConfiguration('sync'); - let allKeysUpdated = new Array>(); - - return new Promise((resolve, reject) => { - - let keys = Object.keys(setting); - keys.forEach(async keyName => { - if ((keyName == "lastDownload" || keyName == "lastUpload") && setting[keyName]) { - try { - let zz = new Date(setting[keyName]); - setting[keyName] = zz; - } catch (e) { - setting[keyName] = new Date(); - } - } - if (setting[keyName] == null) { - setting[keyName] = ""; - } - if (keyName.toLowerCase() == "token") { - allKeysUpdated.push(me.context.globalState.update("synctoken", setting[keyName])); - } - else { - allKeysUpdated.push(config.update(keyName, setting[keyName], true)); - } - }); + const token = ((await vscode.window.showInputBox(opt)) || "").trim(); - Promise.all(allKeysUpdated).then(function (a) { - - if (me.context.globalState.get('syncCounter')) { - let counter = me.context.globalState.get('syncCounter'); - let count: number = parseInt(String(counter)); - if (count % 450 == 0) { - me.DonateMessage(); - } - count = count + 1; - me.context.globalState.update("syncCounter", count) - } - else { - me.context.globalState.update("syncCounter", 1) - } - resolve(true); - }, function (b: any) { - Commons.LogException(b, me.ERROR_MESSAGE, true); - reject(false); - }); - }); + if (token && token !== "esc") { + sett.token = token; + const saved = await this.SetCustomSettings(sett); + if (saved) { + vscode.window.setStatusBarMessage( + localize("common.info.tokenSaved"), + 1000 + ); + } } - public DonateMessage(): void { - const donateNow = localize("common.action.donate"); - const writeReview = localize("common.action.writeReview"); - vscode.window.showInformationMessage(localize("common.info.donate"), donateNow, writeReview).then((res) => { - if (res == donateNow) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=4W3EWHHBSYMM8&lc=IE&item_name=Code%20Settings%20Sync&item_number=visual%20studio%20code%20settings%20sync¤cy_code=USD&bn=PP-DonationsBF:btn_donate_SM.gif:NonHosted')); - } else if (res == writeReview) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync#review-details')); - } - }); + return token; + } + public async GetGistAndSave(sett: ExtensionConfig): Promise { + const opt = Commons.GetInputBox(false); + + const gist = ((await vscode.window.showInputBox(opt)) || "").trim(); + + if (gist && gist !== "esc") { + sett.gist = gist; + const saved = await this.SaveSettings(sett); + if (saved) { + vscode.window.setStatusBarMessage( + localize("common.info.gistSaved"), + 1000 + ); + } + return gist; + } + } + + /** + * IgnoreSettings + */ + public async GetIgnoredSettings(settings: string[]): Promise { + const ignoreSettings: object = {}; + const config = vscode.workspace.getConfiguration(); + const keysUpdated: Array> = []; + + for (const key of settings) { + let keyValue: object = null; + keyValue = config.get(key, null); + if (keyValue !== null) { + ignoreSettings[key] = keyValue; + keysUpdated.push(config.update(key, undefined, true)); + } } - public GetSettings(): ExtensionConfig { - var me = this; - let settings = new ExtensionConfig(); - let keys = Object.keys(settings); + await Promise.all(keysUpdated); - keys.forEach(key => { - if (key != 'token') { - settings[key] = vscode.workspace.getConfiguration("sync")[key]; - } - }); - settings.gist = settings.gist.trim(); - return settings; - } + return ignoreSettings; + } - public async GetTokenAndSave(sett: CustomSettings): Promise { - var me = this; - var opt = Commons.GetInputBox(true); - return new Promise((resolve, reject) => { - (function getToken() { - vscode.window.showInputBox(opt).then(async (token) => { - if (token && token.trim()) { - token = token.trim(); - if (token != 'esc') { - sett.token = token; - await me.SetCustomSettings(sett).then(function (saved: boolean) { - if (saved) { - vscode.window.setStatusBarMessage(localize("common.info.tokenSaved"), 1000); - } - resolve(token); - }, function (err: any) { - reject(err); - }); - } - } - }); - }()); - }); + /** + * RestoreIgnoredSettings + */ + public SetIgnoredSettings(ignoredSettings: object): void { + const config = vscode.workspace.getConfiguration(); + const keysUpdated: Array> = []; + for (const key of Object.keys(ignoredSettings)) { + keysUpdated.push(config.update(key, ignoredSettings[key], true)); } - public async GetGistAndSave(sett: ExtensionConfig): Promise { - var me = this; - var opt = Commons.GetInputBox(false); - return new Promise((resolve, reject) => { - (function getGist() { - vscode.window.showInputBox(opt).then(async (gist) => { - if (gist && gist.trim()) { - gist = gist.trim(); - if (gist != 'esc') { - sett.gist = gist.trim(); - await me.SaveSettings(sett).then(function (saved: boolean) { - if (saved) { - vscode.window.setStatusBarMessage(localize("common.info.gistSaved"), 1000); - } - resolve(gist); - }, function (err: any) { - reject(err); - }); - } - } - }); - })(); - }); + } + + /** + * AskGistName + */ + public async AskGistName(): Promise { + return vscode.window.showInputBox({ + prompt: localize("common.prompt.multipleGist"), + ignoreFocusOut: true, + placeHolder: localize("common.placeholder.multipleGist") + }); + } + + public ShowSummaryOutput( + upload: boolean, + files: File[], + removedExtensions: ExtensionInformation[], + addedExtensions: ExtensionInformation[], + ignoredExtensions: ExtensionInformation[], + syncSettings: LocalConfig + ) { + if (Commons.outputChannel === null) { + Commons.outputChannel = vscode.window.createOutputChannel( + "Code Settings Sync" + ); } - public static GetInputBox(token: boolean) { - - if (token) { - let options: vscode.InputBoxOptions = { - placeHolder: localize("common.placeholder.enterGithubAccessToken"), - password: false, - prompt: localize("common.prompt.enterGithubAccessToken"), - ignoreFocusOut: true - }; - return options; - } - else { - let options: vscode.InputBoxOptions = { - placeHolder: localize("common.placeholder.enterGistId"), - password: false, - prompt: localize("common.prompt.enterGistId"), - ignoreFocusOut: true - }; - return options; - } - }; - - - /** - * IgnoreSettings - */ - public async GetIgnoredSettings(settings: Array): Promise { - let ignoreSettings: Object = new Object(); - return new Promise((resolve, reject) => { - let config = vscode.workspace.getConfiguration(); - let keysUpdated = new Array>(); - settings.forEach(async (key: string, index: number) => { - let keyValue: Object = null; - keyValue = config.get(key, null); - if (keyValue != null) { - ignoreSettings[key] = keyValue; - keysUpdated.push(config.update(key, undefined, true)); - } - }); - Promise.all(keysUpdated).then((a => { - resolve(ignoreSettings); - }), (rej) => { - rej(null); - }); - }); - + const outputChannel = Commons.outputChannel; + outputChannel.clear(); + outputChannel.appendLine( + `CODE SETTINGS SYNC ${upload ? "UPLOAD" : "DOWNLOAD"} SUMMARY` + ); + outputChannel.appendLine(`Version: ${Environment.getVersion()}`); + outputChannel.appendLine(`--------------------`); + outputChannel.appendLine( + `GitHub Token: ${syncSettings.customConfig.token || "Anonymous"}` + ); + outputChannel.appendLine(`GitHub Gist: ${syncSettings.extConfig.gist}`); + outputChannel.appendLine( + `GitHub Gist Type: ${syncSettings.publicGist ? "Public" : "Secret"}` + ); + outputChannel.appendLine(``); + if (!syncSettings.customConfig.token) { + outputChannel.appendLine( + `Anonymous Gist cannot be edited, the extension will always create a new one during upload.` + ); } - - - /** - * RestoreIgnoredSettings - */ - public SetIgnoredSettings(ignoredSettings: Object): void { - let config = vscode.workspace.getConfiguration(); - let keysUpdated = new Array>(); - Object.keys(ignoredSettings).forEach(async (key: string, index: number) => { - keysUpdated.push(config.update(key, ignoredSettings[key], true)); - }); + outputChannel.appendLine( + `Restarting Visual Studio Code may be required to apply color and file icon theme.` + ); + outputChannel.appendLine(`--------------------`); + + outputChannel.appendLine(`Files ${upload ? "Upload" : "Download"}ed:`); + files.filter(item => item.fileName.indexOf(".") > 0).forEach(item => { + outputChannel.appendLine(` ${item.fileName} > ${item.gistName}`); + }); + + outputChannel.appendLine(``); + outputChannel.appendLine(`Extensions Ignored:`); + + if (!ignoredExtensions || ignoredExtensions.length === 0) { + outputChannel.appendLine(` No extensions ignored.`); + } else { + ignoredExtensions.forEach(extn => { + outputChannel.appendLine(` ${extn.name} v${extn.version}`); + }); } - /** - * AskGistName - */ - public async AskGistName(): Promise { - return new Promise((resolve, reject) => { - vscode.window.showInputBox({ - prompt: localize("common.prompt.multipleGist") - , ignoreFocusOut: true - , placeHolder: localize("common.placeholder.multipleGist") - }).then((value) => { - resolve(value); - }); + outputChannel.appendLine(``); + outputChannel.appendLine(`Extensions Removed:`); + + if (!syncSettings.extConfig.removeExtensions) { + outputChannel.appendLine(` Feature Disabled.`); + } else { + if (!removedExtensions || removedExtensions.length === 0) { + outputChannel.appendLine(` No extensions removed.`); + } else { + removedExtensions.forEach(extn => { + outputChannel.appendLine(` ${extn.name} v${extn.version}`); }); - + } } - public ShowSummmaryOutput(upload: boolean, files: Array, removedExtensions: Array, addedExtensions: Array, ignoredExtensions: Array, syncSettings: LocalConfig) { - if (Commons.outputChannel === null) { - Commons.outputChannel = vscode.window.createOutputChannel("Code Settings Sync"); - } - - const outputChannel = Commons.outputChannel; - outputChannel.clear(); - outputChannel.appendLine(`CODE SETTINGS SYNC ${upload ? "UPLOAD" : "DOWNLOAD"} SUMMARY`); - outputChannel.appendLine(`Version: ${Environment.getVersion()}`); - outputChannel.appendLine(`--------------------`); - outputChannel.appendLine(`GitHub Token: ${syncSettings.customConfig.token || "Anonymous"}`); - outputChannel.appendLine(`GitHub Gist: ${syncSettings.extConfig.gist}`); - outputChannel.appendLine(`GitHub Gist Type: ${syncSettings.publicGist ? "Public" : "Secret"}`); - outputChannel.appendLine(``); - if (!syncSettings.customConfig.token) { - outputChannel.appendLine(`Anonymous Gist cannot be edited, the extension will always create a new one during upload.`); - } - outputChannel.appendLine(`Restarting Visual Studio Code may be required to apply color and file icon theme.`); - outputChannel.appendLine(`--------------------`); - - outputChannel.appendLine(`Files ${upload ? "Upload" : "Download"}ed:`); - files - .filter(item => item.fileName.indexOf(".") > 0) - .forEach(item => { - if (item.fileName != item.gistName) { - if (upload) { - outputChannel.appendLine(` ${item.fileName} > ${item.gistName}`); - } - else { - outputChannel.appendLine(` ${item.gistName} > ${item.fileName}`); - } - } - }); - - outputChannel.appendLine(``); - outputChannel.appendLine(`Extensions Ignored:`); - - if (!ignoredExtensions || ignoredExtensions.length === 0) { - outputChannel.appendLine(` No extensions ignored.`); - } - else { - ignoredExtensions.forEach(extn => { - outputChannel.appendLine(` ${extn.name} v${extn.version}`); - }); - } - - outputChannel.appendLine(``); - outputChannel.appendLine(`Extensions Removed:`); - - if (!syncSettings.extConfig.removeExtensions) { - outputChannel.appendLine(` Feature Disabled.`); - } - else { - if (!removedExtensions || removedExtensions.length === 0) { - outputChannel.appendLine(` No extensions removed.`); - } - else { - removedExtensions.forEach(extn => { - outputChannel.appendLine(` ${extn.name} v${extn.version}`); - }); - } - } - - if (addedExtensions) { - outputChannel.appendLine(``) - outputChannel.appendLine(`Extensions Added:`) + if (addedExtensions) { + outputChannel.appendLine(``); + outputChannel.appendLine(`Extensions Added:`); - if (addedExtensions.length === 0) { - outputChannel.appendLine(` No extensions installed.`); - } + if (addedExtensions.length === 0) { + outputChannel.appendLine(` No extensions installed.`); + } - addedExtensions.forEach(extn => { - outputChannel.appendLine(` ${extn.name} v${extn.version}`); - }); - } + addedExtensions.forEach(extn => { + outputChannel.appendLine(` ${extn.name} v${extn.version}`); + }); + } - outputChannel.appendLine(`--------------------`); - outputChannel.append(`Done.`) - outputChannel.show(true) - }; + outputChannel.appendLine(`--------------------`); + outputChannel.append(`Done.`); + outputChannel.show(true); + } } diff --git a/src/enums.ts b/src/enums.ts index 7c9ae388..6901b632 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -1,13 +1,13 @@ export enum OsType { - Windows = 1, - Linux , - Mac -}; + Windows = 1, + Linux, + Mac +} -export enum SettingType{ - Settings = 1, - Launch , - KeyBindings, - Locale, - Extensions -}; \ No newline at end of file +export enum SettingType { + Settings = 1, + Launch, + KeyBindings, + Locale, + Extensions +} diff --git a/src/environmentPath.ts b/src/environmentPath.ts index 2b133e18..6e822734 100644 --- a/src/environmentPath.ts +++ b/src/environmentPath.ts @@ -1,120 +1,144 @@ "use strict"; -import * as fs from 'fs'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { OsType } from './enums'; +import { exec } from "child_process"; +import * as fs from "fs-extra"; +import * as os from "os"; +import * as path from "path"; +import * as vscode from "vscode"; +import { OsType } from "./enums"; export class Environment { - - public static CURRENT_VERSION: number = 300; - public static getVersion(): string { - var txt2 = Environment.CURRENT_VERSION.toString().slice(0, 1) + "." + Environment.CURRENT_VERSION.toString().slice(1, 2) + "." + Environment.CURRENT_VERSION.toString().slice(2, 3); - return txt2; + public static CURRENT_VERSION: number = 310; + public static getVersion(): string { + return ( + Environment.CURRENT_VERSION.toString().slice(0, 1) + + "." + + Environment.CURRENT_VERSION.toString().slice(1, 2) + + "." + + Environment.CURRENT_VERSION.toString().slice(2, 3) + ); + } + + public isInsiders: boolean = false; + public isOss: boolean = false; + public homeDir: string | null = null; + public USER_FOLDER = null; + + public ExtensionFolder: string = null; + public PATH: string = null; + public OsType: OsType = null; + + public FILE_SETTING: string = null; + public FILE_LAUNCH: string = null; + public FILE_KEYBINDING: string = null; + public FILE_LOCALE: string = null; + public FILE_EXTENSION: string = null; + public FILE_CLOUDSETTINGS: string = null; + public FILE_SYNC_LOCK: string = null; + + public FILE_CUSTOMIZEDSETTINGS_NAME: string = "syncLocalSettings.json"; + public FILE_CUSTOMIZEDSETTINGS: string = null; + + public FILE_SETTING_NAME: string = "settings.json"; + public FILE_LAUNCH_NAME: string = "launch.json"; + public FILE_KEYBINDING_NAME: string = "keybindings.json"; + public FILE_KEYBINDING_MAC: string = "keybindingsMac.json"; + public FILE_KEYBINDING_DEFAULT: string = "keybindings.json"; + public FILE_EXTENSION_NAME: string = "extensions.json"; + public FILE_LOCALE_NAME: string = "locale.json"; + public FILE_SYNC_LOCK_NAME: string = "sync.lock"; + + public FILE_CLOUDSETTINGS_NAME: string = "cloudSettings"; + + public FOLDER_SNIPPETS: string = null; + public APP_SUMMARY_NAME: string = "syncSummary.txt"; + public APP_SUMMARY: string = null; + + constructor(private context: vscode.ExtensionContext) { + this.isInsiders = /insiders/.test(this.context.asAbsolutePath("")); + this.isOss = /\boss\b/.test(this.context.asAbsolutePath("")); + const isXdg = + !this.isInsiders && + !!this.isOss && + process.platform === "linux" && + !!process.env.XDG_DATA_HOME; + this.homeDir = isXdg + ? process.env.XDG_DATA_HOME + : process.env[process.platform === "win32" ? "USERPROFILE" : "HOME"]; + const configSuffix = `${isXdg ? "" : "."}vscode${ + this.isInsiders ? "-insiders" : this.isOss ? "-oss" : "" + }`; + this.ExtensionFolder = path.join(this.homeDir, configSuffix, "extensions"); + + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + this.PATH = process.env.APPDATA; + this.OsType = OsType.Windows; + + if (!this.PATH) { + if (process.platform === "darwin") { + this.PATH = process.env.HOME + "/Library/Application Support"; + this.OsType = OsType.Mac; + } else if (process.platform === "linux") { + this.PATH = + isXdg && !!process.env.XDG_CONFIG_HOME + ? process.env.XDG_CONFIG_HOME + : os.homedir() + "/.config"; + this.OsType = OsType.Linux; + } else { + this.PATH = "/var/local"; + this.OsType = OsType.Linux; + } } - private context: vscode.ExtensionContext; - public isInsiders = null; - public isOss = null; - public homeDir = null; - public USER_FOLDER = null; - - public ExtensionFolder: string = null; - public PATH = null; - public OsType: OsType = null; - - public FILE_SETTING: string = null; - public FILE_LAUNCH: string = null; - public FILE_KEYBINDING: string = null; - public FILE_LOCALE: string = null; - public FILE_EXTENSION: string = null; - public FILE_CLOUDSETTINGS: string = null; - public FILE_SYNC_LOCK: string = null; - - public FILE_CUSTOMIZEDSETTINGS_NAME: string = "syncLocalSettings.json"; - public FILE_CUSTOMIZEDSETTINGS: string = null; - - public FILE_SETTING_NAME: string = "settings.json"; - public FILE_LAUNCH_NAME: string = "launch.json"; - public FILE_KEYBINDING_NAME: string = "keybindings.json"; - public FILE_KEYBINDING_MAC: string = "keybindingsMac.json"; - public FILE_KEYBINDING_DEFAULT: string = "keybindings.json"; - public FILE_EXTENSION_NAME: string = "extensions.json"; - public FILE_LOCALE_NAME: string = "locale.json"; - public FILE_SYNC_LOCK_NAME: string = "sync.lock"; - - public FILE_CLOUDSETTINGS_NAME: string = "cloudSettings"; - - public FOLDER_SNIPPETS: string = null; - public APP_SUMMARY_NAME: string = "syncSummary.txt"; - public APP_SUMMARY: string = null; - - constructor(context: vscode.ExtensionContext) { - var os = require("os"); - this.context = context; - this.isInsiders = /insiders/.test(context.asAbsolutePath("")); - this.isOss = /\boss\b/.test(context.asAbsolutePath("")); - const isXdg = !this.isInsiders && !!this.isOss && process.platform === 'linux' && !!process.env.XDG_DATA_HOME - this.homeDir = isXdg - ? process.env.XDG_DATA_HOME - : process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; - const configSuffix = `${isXdg ? '' : '.'}vscode${this.isInsiders ? '-insiders' : this.isOss ? '-oss' : ''}` - this.ExtensionFolder = path.join(this.homeDir, configSuffix, 'extensions'); - - //console.log(os.type()); - - process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" - this.PATH = process.env.APPDATA; - this.OsType = OsType.Windows; - - if (!this.PATH) { - if (process.platform == 'darwin') { - this.PATH = process.env.HOME + '/Library/Application Support'; - this.OsType = OsType.Mac; - } - else if (process.platform == 'linux') { - this.PATH = isXdg && !!process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : os.homedir() + '/.config'; - this.OsType = OsType.Linux; - } else { - this.PATH = '/var/local'; - this.OsType = OsType.Linux; - } - } - - if (this.OsType == OsType.Linux) { - let myExt = "chmod +x " + this.ExtensionFolder + "/Shan.code-settings-sync-" + Environment.getVersion() + "/node_modules/opn/xdg-open"; - var exec = require('child_process').exec; - exec(myExt, function (error, stdout, stderr) { - //debugger; - // command output is in stdout - }); - } - - const possibleCodePaths = [this.isInsiders ? '/Code - Insiders' : this.isOss ? '/Code - OSS' : '/Code']; - for (const _path of possibleCodePaths) { - try { - fs.statSync(this.PATH + _path); - this.PATH = this.PATH + _path; - break; - } catch (e) { - console.error("Error :" + _path); - console.error(e); - } - } - this.USER_FOLDER = this.PATH.concat("/User/"); - - this.FILE_EXTENSION = this.PATH.concat("/User/", this.FILE_EXTENSION_NAME); - this.FILE_SETTING = this.PATH.concat("/User/", this.FILE_SETTING_NAME); - this.FILE_LAUNCH = this.PATH.concat("/User/", this.FILE_LAUNCH_NAME); - this.FILE_KEYBINDING = this.PATH.concat("/User/", this.FILE_KEYBINDING_NAME); - this.FILE_LOCALE = this.PATH.concat("/User/", this.FILE_LOCALE_NAME); - this.FOLDER_SNIPPETS = this.PATH.concat("/User/snippets/"); - this.APP_SUMMARY = this.PATH.concat("/User/", this.APP_SUMMARY_NAME); - this.FILE_CLOUDSETTINGS = this.PATH.concat("/User/", this.FILE_CLOUDSETTINGS_NAME); - this.FILE_CUSTOMIZEDSETTINGS = this.PATH.concat("/User/", this.FILE_CUSTOMIZEDSETTINGS_NAME); - this.FILE_SYNC_LOCK = this.PATH.concat("/User/", this.FILE_SYNC_LOCK_NAME); - + if (this.OsType === OsType.Linux) { + const myExt = + "chmod +x " + + this.ExtensionFolder + + "/Shan.code-settings-sync-" + + Environment.getVersion() + + "/node_modules/opn/xdg-open"; + exec(myExt, () => { + // command output is in stdout + }); } - + const possibleCodePaths = [ + this.isInsiders + ? "/Code - Insiders" + : this.isOss + ? "/Code - OSS" + : "/Code" + ]; + for (const possibleCodePath of possibleCodePaths) { + try { + fs.statSync(this.PATH + possibleCodePath); + this.PATH = this.PATH + possibleCodePath; + break; + } catch (e) { + console.error("Error :" + possibleCodePath); + console.error(e); + } + } + this.USER_FOLDER = this.PATH.concat("/User/"); + + this.FILE_EXTENSION = this.PATH.concat("/User/", this.FILE_EXTENSION_NAME); + this.FILE_SETTING = this.PATH.concat("/User/", this.FILE_SETTING_NAME); + this.FILE_LAUNCH = this.PATH.concat("/User/", this.FILE_LAUNCH_NAME); + this.FILE_KEYBINDING = this.PATH.concat( + "/User/", + this.FILE_KEYBINDING_NAME + ); + this.FILE_LOCALE = this.PATH.concat("/User/", this.FILE_LOCALE_NAME); + this.FOLDER_SNIPPETS = this.PATH.concat("/User/snippets/"); + this.APP_SUMMARY = this.PATH.concat("/User/", this.APP_SUMMARY_NAME); + this.FILE_CLOUDSETTINGS = this.PATH.concat( + "/User/", + this.FILE_CLOUDSETTINGS_NAME + ); + this.FILE_CUSTOMIZEDSETTINGS = this.PATH.concat( + "/User/", + this.FILE_CUSTOMIZEDSETTINGS_NAME + ); + this.FILE_SYNC_LOCK = this.PATH.concat("/User/", this.FILE_SYNC_LOCK_NAME); + } } diff --git a/src/extension.ts b/src/extension.ts index 24854f40..49cb9cb4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,786 +1,44 @@ -'use strict'; +"use strict"; -import * as vscode from 'vscode'; -import { PluginService, ExtensionInformation } from './service/pluginService'; -import * as path from 'path'; -import { Environment } from './environmentPath'; -import { File, FileService } from './service/fileService'; -import Commons from './commons'; -import { GitHubService } from './service/githubService'; -import { ExtensionConfig, LocalConfig, CloudSetting, CustomSettings, KeyValue } from './setting'; -import { OsType, SettingType } from './enums'; -import localize from './localize'; +import * as vscode from "vscode"; +import { init as initLocalize } from "./localize"; +import { Sync } from "./sync"; export async function activate(context: vscode.ExtensionContext) { - - var fs = require('fs'); - const lockfile = require('proper-lockfile'); - - var en: Environment = new Environment(context); - var common: Commons = new Commons(en, context); - - let lockExist: boolean = await FileService.FileExists(en.FILE_SYNC_LOCK); - if (!lockExist) { - fs.closeSync(fs.openSync(en.FILE_SYNC_LOCK, 'w')); - } - - let locked: boolean = lockfile.checkSync(en.FILE_SYNC_LOCK); - if (locked) { - lockfile.unlockSync(en.FILE_SYNC_LOCK); - } - - await common.StartMigrationProcess(); - let startUpSetting: ExtensionConfig = await common.GetSettings(); - let startUpCustomSetting: CustomSettings = await common.GetCustomSettings(); - - if (startUpSetting) { - let tokenAvailable: boolean = (startUpCustomSetting.token != null) && (startUpCustomSetting.token != ""); - let gistAvailable: boolean = (startUpSetting.gist != null) && (startUpSetting.gist != ""); - - if (gistAvailable == true && startUpSetting.autoDownload == true) { - vscode.commands.executeCommand('extension.downloadSettings').then(suc => { - if (startUpSetting.autoUpload && tokenAvailable && gistAvailable) { - common.StartWatch(); - } - }); - } - if (startUpSetting.autoUpload && tokenAvailable && gistAvailable) { - common.StartWatch(); - } - } - - var updateSettings = vscode.commands.registerCommand('extension.updateSettings', async function () { - - let args = arguments; - let en: Environment = new Environment(context); - let common: Commons = new Commons(en, context); - let myGi: GitHubService = null; - let localConfig: LocalConfig = new LocalConfig(); - let allSettingFiles = new Array(); - let uploadedExtensions = new Array(); - let ignoredExtensions = new Array(); - let dateNow: Date = new Date(); - common.CloseWatch(); - let ignoreSettings = new Object(); - - try { - localConfig = await common.InitalizeSettings(true, false); - localConfig.publicGist = false; - if (args.length > 0) { - if (args[0] == "publicGIST") { - localConfig.publicGist = true; - } - } - - myGi = new GitHubService(localConfig.customConfig.token); - //ignoreSettings = await common.GetIgnoredSettings(localConfig.customConfig.ignoreUploadSettings); - await startGitProcess(localConfig.extConfig, localConfig.customConfig); - //await common.SetIgnoredSettings(ignoreSettings); - - } catch (error) { - Commons.LogException(error, common.ERROR_MESSAGE, true); - return; - } - - async function startGitProcess(syncSetting: ExtensionConfig, customSettings: CustomSettings) { - - vscode.window.setStatusBarMessage(localize("cmd.updateSettings.info.uploading"), 2000); - - if (customSettings.downloadPublicGist) { - if (customSettings.token == null || customSettings.token == "") { - vscode.window.showInformationMessage(localize("cmd.updateSettings.warning.noToken")); - - return; - } - } - - syncSetting.lastUpload = dateNow; - vscode.window.setStatusBarMessage(localize('cmd.updateSettings.info.readding'), 2000); - - - - // var remoteList = ExtensionInformation.fromJSONList(file.content); - // var deletedList = PluginService.GetDeletedExtensions(uploadedExtensions); - if (syncSetting.syncExtensions) { - uploadedExtensions = PluginService.CreateExtensionList(); - if (customSettings.ignoreExtensions && customSettings.ignoreExtensions.length > 0) { - uploadedExtensions = uploadedExtensions.filter(extension => { - if (customSettings.ignoreExtensions.includes(extension.name)) { - ignoredExtensions.push(extension); - return false; - } - return true; - }) - } - uploadedExtensions.sort(function (a, b) { - return a.name.localeCompare(b.name); - }); - let fileName = en.FILE_EXTENSION_NAME; - let filePath = en.FILE_EXTENSION; - let fileContent = JSON.stringify(uploadedExtensions, undefined, 2); - let file: File = new File(fileName, fileContent, filePath, fileName); - allSettingFiles.push(file); - } - - - let contentFiles: Array = new Array(); - contentFiles = await FileService.ListFiles(en.USER_FOLDER, 0, 2, customSettings.supportedFileExtensions); - - let customExist: boolean = await FileService.FileExists(en.FILE_CUSTOMIZEDSETTINGS); - if (customExist) { - customSettings = await common.GetCustomSettings(); - contentFiles = contentFiles.filter((file: File, index: number) => { - let a: boolean = file.fileName != en.FILE_CUSTOMIZEDSETTINGS_NAME; - return a; - }); - - if (customSettings.ignoreUploadFiles.length > 0) { - contentFiles = contentFiles.filter((file: File, index: number) => { - let a: boolean = customSettings.ignoreUploadFiles.indexOf(file.fileName) == -1 && file.fileName != en.FILE_CUSTOMIZEDSETTINGS_NAME; - return a; - }); - } - if (customSettings.ignoreUploadFolders.length > 0) { - contentFiles = contentFiles.filter((file: File, index: number) => { - let matchedFolders = customSettings.ignoreUploadFolders.filter((folder) => { - return file.filePath.indexOf(folder) == -1; - }); - return matchedFolders.length > 0; - }); - } - } - else { - Commons.LogException(null, common.ERROR_MESSAGE, true); - return; - } - - contentFiles.forEach(snippetFile => { - if (snippetFile.fileName != en.APP_SUMMARY_NAME && snippetFile.fileName != en.FILE_KEYBINDING_MAC) { - if (snippetFile.content != "") { - if (snippetFile.fileName == en.FILE_KEYBINDING_NAME) { - var destinationKeyBinding: string = ""; - if (en.OsType == OsType.Mac) { - destinationKeyBinding = en.FILE_KEYBINDING_MAC; - } - else { - destinationKeyBinding = en.FILE_KEYBINDING_DEFAULT; - } - snippetFile.gistName = destinationKeyBinding; - } - allSettingFiles.push(snippetFile); - } - } - }); - - var extProp: CloudSetting = new CloudSetting(); - extProp.lastUpload = dateNow; - var fileName: string = en.FILE_CLOUDSETTINGS_NAME; - var fileContent: string = JSON.stringify(extProp); - var file: File = new File(fileName, fileContent, "", fileName); - allSettingFiles.push(file); - - let completed: boolean = false; - let newGIST: boolean = false; - - if (syncSetting.gist == null || syncSetting.gist === "") { - if (syncSetting.askGistName) { - customSettings.gistDescription = await common.AskGistName(); - } - newGIST = true; - await myGi.CreateEmptyGIST(localConfig.publicGist, customSettings.gistDescription).then(async function (gistID: string) { - if (gistID) { - syncSetting.gist = gistID; - vscode.window.setStatusBarMessage(localize("cmd.updateSettings.info.newGistCreated"), 2000); - } - else { - vscode.window.showInformationMessage(localize("cmd.updateSettings.error.newGistCreateFail")); - return; - } - }, function (error: any) { - Commons.LogException(error, common.ERROR_MESSAGE, true); - return; - }); - } - - await myGi.ReadGist(syncSetting.gist).then(async function (gistObj: any) { - - if (gistObj) { - if (gistObj.data.owner != null) { - let gistOwnerName: string = gistObj.data.owner.login.trim(); - if (myGi.userName != null) { - let userName: string = myGi.userName.trim(); - if (gistOwnerName != userName) { - Commons.LogException(null, "Sync : You cant edit GIST for user : " + gistObj.data.owner.login, true, function () { - console.log("Sync : Current User : " + "'" + userName + "'"); - console.log("Sync : Gist Owner User : " + "'" + gistOwnerName + "'"); - }); - return; - } - } - - } - if (gistObj.public == true) { - localConfig.publicGist = true; - } - - vscode.window.setStatusBarMessage(localize('cmd.updateSettings.info.uploadingFile'), 3000); - gistObj = myGi.UpdateGIST(gistObj, allSettingFiles); - - await myGi.SaveGIST(gistObj.data).then(async function (saved: boolean) { - if (saved) { - completed = true; - } - else { - vscode.window.showErrorMessage(localize("cmd.updateSettings.error.gistNotSave")); - return; - } - }, function (error: any) { - Commons.LogException(error, common.ERROR_MESSAGE, true); - return; - }); - } - else { - vscode.window.showErrorMessage(localize("cmd.updateSettings.error.readGistFail", syncSetting.gist)); - return; - } - }, function (gistReadError: any) { - Commons.LogException(gistReadError, common.ERROR_MESSAGE, true); - return; - }); - - if (completed) { - await common.SaveSettings(syncSetting).then(function (added: boolean) { - if (added) { - if (newGIST) { - vscode.window.showInformationMessage(localize('cmd.updateSettings.info.uploadingDone', syncSetting.gist)); - } - - if (localConfig.publicGist) { - vscode.window.showInformationMessage(localize('cmd.updateSettings.info.shareGist')); - } - - if (!syncSetting.quietSync) { - common.ShowSummmaryOutput(true, allSettingFiles, null, uploadedExtensions, ignoredExtensions, localConfig); - vscode.window.setStatusBarMessage("").dispose(); - } - else { - vscode.window.setStatusBarMessage("").dispose(); - vscode.window.setStatusBarMessage(localize('cmd.updateSettings.info.uploadingSuccess'), 5000); - } - if (syncSetting.autoUpload) { - common.StartWatch(); - } - } - }, function (err: any) { - Commons.LogException(err, common.ERROR_MESSAGE, true); - return; - }); - } - } - }); - - var downloadSettings = vscode.commands.registerCommand('extension.downloadSettings', async function () { - - var en: Environment = new Environment(context); - var common: Commons = new Commons(en, context); - var myGi: GitHubService = null; - var localSettings: LocalConfig = new LocalConfig(); - let ignoreSettings = new Object(); - common.CloseWatch(); - - try { - localSettings = await common.InitalizeSettings(true, true); - //ignoreSettings = await common.GetIgnoredSettings(localSettings.customConfig.ignoreUploadSettings); - await StartDownload(localSettings.extConfig, localSettings.customConfig); - //await common.SetIgnoredSettings(ignoreSettings); - - } catch (error) { - Commons.LogException(error, common.ERROR_MESSAGE, true); - return; - } - - async function StartDownload(syncSetting: ExtensionConfig, customSettings: CustomSettings) { - - myGi = new GitHubService(customSettings.token); - vscode.window.setStatusBarMessage("").dispose(); - vscode.window.setStatusBarMessage(localize("cmd.downloadSettings.info.readdingOnline"), 2000); - - myGi.ReadGist(syncSetting.gist).then(async function (res: any) { - - var addedExtensions: Array = new Array(); - var deletedExtensions: Array = new Array(); - var updatedFiles: Array = new Array(); - var actionList = new Array>(); - - if (res) { - if (res.data.public == true) { - localSettings.publicGist = true; - } - var keys = Object.keys(res.data.files); - if (keys.indexOf(en.FILE_CLOUDSETTINGS_NAME) > -1) { - var cloudSettGist: Object = JSON.parse(res.data.files[en.FILE_CLOUDSETTINGS_NAME].content); - var cloudSett: CloudSetting = Object.assign(new CloudSetting(), cloudSettGist);; - - let lastUploadStr: string = (syncSetting.lastUpload) ? syncSetting.lastUpload.toString() : ""; - let lastDownloadStr: string = (syncSetting.lastDownload) ? syncSetting.lastDownload.toString() : ""; - - var upToDate: boolean = false; - if (lastDownloadStr != "") { - upToDate = new Date(lastDownloadStr).getTime() === new Date(cloudSett.lastUpload).getTime(); - } - - if (lastUploadStr != "") { - upToDate = upToDate || new Date(lastUploadStr).getTime() === new Date(cloudSett.lastUpload).getTime(); - } - - if (!syncSetting.forceDownload) { - if (upToDate) { - vscode.window.setStatusBarMessage("").dispose(); - vscode.window.setStatusBarMessage(localize("cmd.downloadSettings.info.gotLatestVersion"), 5000); - return; - } - } - syncSetting.lastDownload = cloudSett.lastUpload; - } - - keys.forEach(gistName => { - if (res.data.files[gistName]) { - if (res.data.files[gistName].content) { - if (gistName.indexOf(".") > -1) { - if (en.OsType == OsType.Mac && gistName == en.FILE_KEYBINDING_DEFAULT) { - return; - } - if (en.OsType != OsType.Mac && gistName == en.FILE_KEYBINDING_MAC) { - return; - } - var f: File = new File(gistName, res.data.files[gistName].content, null, gistName); - updatedFiles.push(f); - } - } - } - else { - console.log(gistName + " key in response is empty."); - } - }); - - for (var index = 0; index < updatedFiles.length; index++) { - - var file: File = updatedFiles[index]; - var path: string = null; - var writeFile: boolean = false; - var content: string = file.content; - - if (content != "") { - if (file.gistName == en.FILE_EXTENSION_NAME) { - if (syncSetting.syncExtensions) { - var extDelStatus: Array> = new Array>(); - - if (customSettings.ignoreExtensions && customSettings.ignoreExtensions.length) { - const extList = ExtensionInformation.fromJSONList(content) - const newExtList = extList.filter(extension => - !customSettings.ignoreExtensions.includes(extension.name) - ) - content = JSON.stringify(newExtList) - } - - if (syncSetting.removeExtensions) { - try { - deletedExtensions = await PluginService.DeleteExtensions(content, en.ExtensionFolder); - } - catch (uncompletedExtensions) { - vscode.window.showErrorMessage(localize("cmd.downloadSettings.error.removeExtFail")); - deletedExtensions = uncompletedExtensions; - } - - } - try { - addedExtensions = await PluginService.InstallExtensions(content, en.ExtensionFolder, function (message: string, dispose: boolean) { - //TODO: - if (dispose) { - vscode.window.setStatusBarMessage(message, 2000); - } - else { - vscode.window.setStatusBarMessage(message, 5000); - } - }); - } - catch (extensions) { - addedExtensions = extensions; - } - } - } - else { - writeFile = true; - if (file.gistName == en.FILE_KEYBINDING_DEFAULT || file.gistName == en.FILE_KEYBINDING_MAC) { - let test: string = ""; - en.OsType == OsType.Mac ? test = en.FILE_KEYBINDING_MAC : test = en.FILE_KEYBINDING_DEFAULT; - if (file.gistName != test) { - writeFile = false; - } - } - if (writeFile) { - if (file.gistName == en.FILE_KEYBINDING_MAC) { - file.fileName = en.FILE_KEYBINDING_DEFAULT; - } - let filePath: string = await FileService.CreateDirTree(en.USER_FOLDER, file.fileName); - await actionList.push(FileService.WriteFile(filePath, content).then( - function (added: boolean) { - //TODO : add Name attribute in File and show information message here with name , when required. - }, function (error: any) { - Commons.LogException(error, common.ERROR_MESSAGE, true); - return; - } - )); - } - } - } - } - } - else { - Commons.LogException(res, "Sync : Unable to Read Gist.", true); - } - - Promise.all(actionList) - .then(async function () { - - // if (!syncSetting.showSummary) { - // if (missingList.length == 0) { - // //vscode.window.showInformationMessage("No extension need to be installed"); - // } - // else { - // //extension message when summary is turned off - // vscode.window.showInformationMessage("Sync : " + missingList.length + " extensions installed Successfully, Restart Required."); - // } - // if (deletedExtensions.length > 0) { - // vscode.window.showInformationMessage("Sync : " + deletedExtensions.length + " extensions deleted Successfully, Restart Required."); - // } - // } - - await common.SaveSettings(syncSetting).then(async function (added: boolean) { - if (added) { - if (!syncSetting.quietSync) { - common.ShowSummmaryOutput(false, updatedFiles, deletedExtensions, addedExtensions, null, localSettings); - vscode.window.setStatusBarMessage("").dispose(); - } - else { - vscode.window.setStatusBarMessage("").dispose(); - vscode.window.setStatusBarMessage("Sync : Download Complete.", 5000); - } - if (Object.keys(customSettings.replaceCodeSettings).length > 0) { - - let config = vscode.workspace.getConfiguration(); - let keysDefined: Array = Object.keys(customSettings.replaceCodeSettings); - keysDefined.forEach((key: string, index: number) => { - let value: string = customSettings.replaceCodeSettings[key]; - let c: any = value === "" ? undefined : value; - config.update(key, c, true); - }); - } - if (syncSetting.autoUpload) { - common.StartWatch(); - } - } - else { - vscode.window.showErrorMessage("Sync : Unable to save extension settings file.") - } - }, function (errSave: any) { - Commons.LogException(errSave, common.ERROR_MESSAGE, true); - return; - }); - }) - .catch(function (e) { - Commons.LogException(e, common.ERROR_MESSAGE, true); - }); - }, function (err: any) { - Commons.LogException(err, common.ERROR_MESSAGE, true); - return; - }); - } - }); - - var resetSettings = vscode.commands.registerCommand('extension.resetSettings', async () => { - - var extSettings: ExtensionConfig = null; - var localSettings: CustomSettings = null; - await Init(); - - async function Init() { - vscode.window.setStatusBarMessage(localize("cmd.resetSettings.info.resetting"), 2000); - - try { - var en: Environment = new Environment(context); - var common: Commons = new Commons(en, context); - - extSettings = new ExtensionConfig(); - localSettings = new CustomSettings(); - - let extSaved: boolean = await common.SaveSettings(extSettings); - let customSaved: boolean = await common.SetCustomSettings(localSettings); - let lockExist: boolean = await FileService.FileExists(en.FILE_SYNC_LOCK); - - if (!lockExist) { - fs.closeSync(fs.openSync(en.FILE_SYNC_LOCK, 'w')); - } - - let locked: boolean = lockfile.checkSync(en.FILE_SYNC_LOCK); - if (locked) { - lockfile.unlockSync(en.FILE_SYNC_LOCK); - } - - if (extSaved && customSaved) { - vscode.window.showInformationMessage(localize("cmd.resetSettings.info.settingClear")); - } - } - catch (err) { - Commons.LogException(err, "Sync : Unable to clear settings. Error Logged on console. Please open an issue.", true); - } - } - }); - - var howSettings = vscode.commands.registerCommand('extension.HowSettings', async () => { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('http://shanalikhan.github.io/2015/12/15/Visual-Studio-Code-Sync-Settings.html')); - }); - - var otherOptions = vscode.commands.registerCommand('extension.otherOptions', async () => { - var en: Environment = new Environment(context); - var common: Commons = new Commons(en, context); - var setting: ExtensionConfig = await common.GetSettings(); - let customSettings: CustomSettings = await common.GetCustomSettings(); - var localSetting: LocalConfig = new LocalConfig(); - var tokenAvailable: boolean = customSettings.token != null && customSettings.token != ""; - var gistAvailable: boolean = setting.gist != null && setting.gist != ""; - - let items: Array = new Array(); - items.push(localize("cmd.otherOptions.editLocalSetting")); - items.push(localize("cmd.otherOptions.shareSetting")); - items.push(localize("cmd.otherOptions.downloadSetting")); - items.push(localize("cmd.otherOptions.toggleForceDownload")); - items.push(localize("cmd.otherOptions.toggleAutoUpload")); - items.push(localize("cmd.otherOptions.toggleAutoDownload")); - items.push(localize("cmd.otherOptions.toggleSummaryPage")); - items.push(localize("cmd.otherOptions.preserve")); - items.push(localize("cmd.otherOptions.joinCommunity")); - items.push(localize("cmd.otherOptions.openIssue")); - items.push(localize("cmd.otherOptions.releaseNotes")); - - var selectedItem: Number = 0; - var settingChanged: boolean = false; - - vscode.window.showQuickPick(items).then(async (resolve: string) => { - - switch (resolve) { - case items[0]: { - //extension local settings - var file: vscode.Uri = vscode.Uri.file(en.FILE_CUSTOMIZEDSETTINGS); - fs.openSync(file.fsPath, 'r'); - await vscode.workspace.openTextDocument(file).then((a: vscode.TextDocument) => { - vscode.window.showTextDocument(a, vscode.ViewColumn.One, true); - }); - break; - } - case items[1]: { - //share public gist - await vscode.window.showInformationMessage(localize("cmd.otherOptions.shareSetting.beforeConfirm"), "Yes").then(async (resolve) => { - if (resolve == "Yes") { - localSetting.publicGist = true; - settingChanged = true; - setting.gist = ""; - selectedItem = 1; - customSettings.downloadPublicGist = false; - let done: boolean = await common.SetCustomSettings(customSettings); - } - }, (reject) => { - return; - }); - break; - } - case items[2]: { - //Download Settings from Public GIST - selectedItem = 2; - customSettings.downloadPublicGist = true; - settingChanged = true; - let done: boolean = await common.SetCustomSettings(customSettings); - break; - } - case items[3]: { - //toggle force download - selectedItem = 3; - settingChanged = true; - if (setting.forceDownload) { - setting.forceDownload = false; - } - else { - setting.forceDownload = true; - } - break; - } - case items[4]: { - //toggle auto upload - selectedItem = 4; - settingChanged = true; - if (setting.autoUpload) { - setting.autoUpload = false; - } - else { - setting.autoUpload = true; - } - break; - } - case items[5]: { - //auto downlaod on startup - selectedItem = 5; - settingChanged = true; - if (!setting) { - vscode.commands.executeCommand('extension.HowSettings'); - return; - } - if (!gistAvailable) { - vscode.commands.executeCommand('extension.HowSettings'); - return; - } - if (setting.autoDownload) { - setting.autoDownload = false; - } - else { - setting.autoDownload = true; - } - break; - } - case items[6]: { - //page summary toggle - selectedItem = 6; - settingChanged = true; - - if (!tokenAvailable || !gistAvailable) { - vscode.commands.executeCommand('extension.HowSettings'); - return; - } - if (setting.quietSync) { - setting.quietSync = false; - } - else { - setting.quietSync = true; - } - break; - } - - case items[7]: { - //preserve - let options: vscode.InputBoxOptions = { - ignoreFocusOut: true, - placeHolder: localize("cmd.otherOptions.preserve.placeholder"), - prompt: localize("cmd.otherOptions.preserve.prompt") - - }; - vscode.window.showInputBox(options).then(async (res) => { - if (res) { - let settingKey: string = res; - let a = vscode.workspace.getConfiguration(); - let val: string = a.get(settingKey); - customSettings.replaceCodeSettings[res] = val; - let done: boolean = await common.SetCustomSettings(customSettings); - if (done) { - if (val == "") { - vscode.window.showInformationMessage(localize("cmd.otherOptions.preserve.info.done1", res)); - } - else { - vscode.window.showInformationMessage(localize("cmd.otherOptions.preserve.info.done1", res, val)); - } - } - } - }); - break; - } - case items[8]: { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk')); - break; - } - case items[9]: { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/shanalikhan/code-settings-sync/issues/new')); - break; - } - case items[10]: { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html')); - break; - } - default: { - break; - } - } - }, (reject) => { - Commons.LogException(reject, "Error", true); - return; - }).then(async (resolve: any) => { - if (settingChanged) { - if (selectedItem == 1) { - common.CloseWatch(); - } - await common.SaveSettings(setting).then(async function (added: boolean) { - if (added) { - switch (selectedItem) { - case 5: { - if (setting.autoDownload) { - vscode.window.showInformationMessage(localize("cmd.otherOptions.toggleAutoDownload.on")); - } - else { - vscode.window.showInformationMessage(localize("cmd.otherOptions.toggleAutoDownload.off")); - } - break; - } - case 6: { - if (!setting.quietSync) { - vscode.window.showInformationMessage(localize("cmd.otherOptions.quietSync.off")); - } - else { - vscode.window.showInformationMessage(localize("cmd.otherOptions.quietSync.on")); - } - break; - } - case 3: { - if (setting.forceDownload) { - vscode.window.showInformationMessage(localize("cmd.otherOptions.toggleForceDownload.on")); - } - else { - vscode.window.showInformationMessage(localize("cmd.otherOptions.toggleForceDownload.off")); - } - break; - } - case 4: { - if (setting.autoUpload) { - vscode.window.showInformationMessage(localize("cmd.otherOptions.toggleAutoUpload.on")); - } - else { - vscode.window.showInformationMessage(localize("cmd.otherOptions.toggleAutoUpload.off")); - } - break; - } - case 1: { - await vscode.commands.executeCommand('extension.updateSettings', "publicGIST"); - break; - } - case 2: { - vscode.window.showInformationMessage(localize("cmd.otherOptions.warning.tokenNotRequire")); - } - } - } - else { - vscode.window.showErrorMessage(localize("cmd.otherOptions.error.toggleFail")); - } - }, function (err: any) { - Commons.LogException(err, "Sync : Unable to toggle. Please open an issue.", true); - return; - }); - } - }, (reject: any) => { - Commons.LogException(reject, "Error", true); - return; - }); - }); - - context.subscriptions.push(updateSettings); - context.subscriptions.push(downloadSettings); - context.subscriptions.push(resetSettings); - context.subscriptions.push(howSettings); - context.subscriptions.push(otherOptions); - + await initLocalize(); + + const sync = new Sync(context); + + sync.bootstrap(); + + context.subscriptions.push( + vscode.commands.registerCommand( + "extension.updateSettings", + sync.upload.bind(sync) + ) + ); + context.subscriptions.push( + vscode.commands.registerCommand( + "extension.downloadSettings", + sync.download.bind(sync) + ) + ); + context.subscriptions.push( + vscode.commands.registerCommand( + "extension.resetSettings", + sync.reset.bind(sync) + ) + ); + context.subscriptions.push( + vscode.commands.registerCommand( + "extension.HowSettings", + sync.how.bind(sync) + ) + ); + context.subscriptions.push( + vscode.commands.registerCommand( + "extension.otherOptions", + sync.advance.bind(sync) + ) + ); } diff --git a/src/localize.ts b/src/localize.ts index 0bed6630..57ca42ce 100644 --- a/src/localize.ts +++ b/src/localize.ts @@ -1,4 +1,4 @@ -import * as fs from "fs"; +import * as fs from "fs-extra"; import * as path from "path"; interface IConfig { @@ -10,19 +10,21 @@ interface ILanguagePack { } export class Localize { - // get language pack when the instance be created - private bundle = this.resolveLanguagePack(); - constructor(private config: IConfig = {}) {} + private bundle: ILanguagePack; + constructor(private options: IConfig = {}) {} /** * translate the key * @param key * @param args */ - public localize(key: string, ...args: any[]) { + public localize(key: string, ...args: any[]): string { const languagePack = this.bundle; const message: string = languagePack[key] || key; return this.format(message, args); } + public async init() { + this.bundle = await this.resolveLanguagePack(); + } /** * format the message * @param message @@ -43,20 +45,21 @@ export class Localize { /** * Get language pack */ - private resolveLanguagePack(): ILanguagePack { + private async resolveLanguagePack(): Promise { + const defaultResvoleLanguage = ".nls.json"; let resolvedLanguage: string = ""; // TODO: it should read the extension root path from context const rootPath = path.join(__dirname, "..", ".."); const file = path.join(rootPath, "package"); - const options = this.config; + const options = this.options; if (!options.locale) { - resolvedLanguage = ".nls.json"; + resolvedLanguage = defaultResvoleLanguage; } else { let locale: string | null = options.locale; while (locale) { const candidate = ".nls." + locale + ".json"; - if (fs.existsSync(file + candidate)) { + if (await fs.pathExists(file + candidate)) { resolvedLanguage = candidate; break; } else { @@ -71,13 +74,25 @@ export class Localize { } } - const languageFilePath = path.join(file + resolvedLanguage); + let defaultLanguageBundle = {}; - if (!fs.existsSync(languageFilePath)) { - return {}; + // if not use default language + // then merger the Language pack + // just in case the resolveLanguage bundle missing the translation and fallback with default language + if (resolvedLanguage !== defaultResvoleLanguage) { + defaultLanguageBundle = require(path.join(file + defaultResvoleLanguage)); } - return require(languageFilePath); + const languageFilePath = path.join(file + resolvedLanguage); + + const isExistResolvedLanguage = await fs.pathExists(languageFilePath); + + const ResolvedLanguageBundle = isExistResolvedLanguage + ? require(languageFilePath) + : {}; + + // merger with default language bundle + return { ...defaultLanguageBundle, ...ResolvedLanguageBundle }; } } @@ -96,4 +111,8 @@ try { const instance = new Localize(config); +const init = instance.init.bind(instance); + export default instance.localize.bind(instance); + +export { init }; diff --git a/src/lockfile.ts b/src/lockfile.ts new file mode 100644 index 00000000..e3df2842 --- /dev/null +++ b/src/lockfile.ts @@ -0,0 +1,34 @@ +import * as lockfile from "lockfile"; +import { Util } from "./util"; + +interface IOptions { + wait?: number; + pollPeriod?: number; + stale?: number; + retries?: number; + retryWait?: number; +} + +export default { + Check, + Lock, + Unlock +}; + +export function Check( + filepath: string, + options: IOptions = {} +): Promise { + return Util.promisify(lockfile.check)(filepath, options); +} + +export function Lock( + filepath: string, + options: IOptions = {} +): Promise { + return Util.promisify(lockfile.lock)(filepath, options); +} + +export function Unlock(filepath: string): Promise { + return Util.promisify(lockfile.unlock)(filepath); +} diff --git a/src/service/fileService.ts b/src/service/fileService.ts index 76f2ed14..04eea8ca 100644 --- a/src/service/fileService.ts +++ b/src/service/fileService.ts @@ -1,206 +1,196 @@ -import { LocalConfig } from '../setting'; "use strict"; -const fs = require('fs'); -const path = require('path'); -export class File { +import * as fs from "fs-extra"; +import * as path from "path"; - constructor(public fileName: string, public content: string, public filePath: string, public gistName: string) { - // this.fileName = file.split('.')[0]; - //this.fileName = file; - } +export class File { + constructor( + public fileName: string, + public content: string, + public filePath: string, + public gistName: string + ) {} } export class FileService { + public static async ReadFile(filePath: string): Promise { + try { + const data = await fs.readFile(filePath, { encoding: "utf8" }); + return data; + } catch (err) { + console.error(err); + throw err; + } + } + + public static async IsDirectory(filepath: string): Promise { + try { + const stat = await fs.lstat(filepath); + return stat.isDirectory(); + } catch (err) { + return false; + } + } - public static async ReadFile(filePath: string): Promise { - return new Promise(async (resolve, reject) => { - - await fs.readFile(filePath, { encoding: 'utf8' }, function (err: any, data: any) { - if (err) { - console.error(err); - reject(err); - } - resolve(data); + public static async GetFile( + filePath: string, + fileName: string + ): Promise { + const fileExists: boolean = await FileService.FileExists(filePath); - }); - }); + if (!fileExists) { + return null; } - public static async IsDirectory(path: string): Promise { - var me: FileService = this; - return new Promise(async (resolve, reject) => { - let d = await fs.lstatSync(path); - if (d.isDirectory()) { - resolve(true); - } - resolve(false); - }); - } + const content = await FileService.ReadFile(filePath); - public static async GetFile(filePath: string, fileName: string): Promise { - var me: FileService = this; - return new Promise(async (resolve, reject) => { - - let fileExists: boolean = await FileService.FileExists(filePath); - if (fileExists) { - FileService.ReadFile(filePath).then(function (content: string) { - if (content != null) { - let pathFromUser: string = filePath.substring(filePath.lastIndexOf("User") + 5, filePath.length); - let arr = new Array(); - if (pathFromUser.indexOf("/")) { - arr = pathFromUser.split("/"); - } - else { - arr = pathFromUser.split(path.sep); - } - let gistName: string = ""; - arr.forEach((element, index) => { - if (index < arr.length - 1) { - gistName += element + "|"; - } - else { - gistName += element; - } - }); - var file: File = new File(fileName, content, filePath, gistName); - resolve(file); - } - resolve(null); - }); - } - else { - resolve(null); - } - }); + if (!content) { + return null; } - public static async WriteFile(filePath: string, data: string): Promise { - return new Promise((resolve, reject) => { - if (data) { - fs.writeFile(filePath, data, (err) => { - if (err) reject(false); - else resolve(true); - }); - } - else { - console.error("Unable to write file. FilePath :" + filePath + " Data :" + data); - reject(false); - } - }); + const pathFromUser: string = filePath.substring( + filePath.lastIndexOf("User") + 5, + filePath.length + ); + + const arr: string[] = pathFromUser.indexOf("/") + ? pathFromUser.split("/") + : pathFromUser.split(path.sep); + + let gistName: string = ""; + + arr.forEach((element, index) => { + if (index < arr.length - 1) { + gistName += element + "|"; + } else { + gistName += element; + } + }); + + const file: File = new File(fileName, content, filePath, gistName); + return file; + } + + public static async WriteFile( + filePath: string, + data: string + ): Promise { + if (!data) { + console.error( + new Error( + "Unable to write file. FilePath :" + filePath + " Data :" + data + ) + ); + return false; } - - public static async ListFiles(directory: string, depth: number, fullDepth: number, fileExtensions: Array): Promise> { - var me = this; - return new Promise>((resolve, reject) => { - fs.readdir(directory, async function (err: any, data: Array) { - if (err) { - console.error(err); - resolve(null); - } - - var files = new Array(); - for (var i = 0; i < data.length; i++) { - let fullPath: string = directory.concat(data[i]); - let isDir: boolean = await FileService.IsDirectory(fullPath); - if (isDir) { - if (depth < fullDepth) { - let filews: Array = await FileService.ListFiles(fullPath + "/", depth + 1, fullDepth, fileExtensions); - filews.forEach(element => { - files.push(element) - }); - } - } - else { - let hasExtension: boolean = fullPath.lastIndexOf(".") > 0 ? true : false; - let allowedFile: boolean = false; - if (hasExtension) { - let extension: string = fullPath.substr(fullPath.lastIndexOf(".") + 1, fullPath.length); - extension = extension.toLowerCase(); - allowedFile = fileExtensions.filter(m => m == extension).length > 0 ? true : false; - } - else { - allowedFile = fileExtensions.filter(m => m == "").length > 0 ? true : false; - } - - if (allowedFile) { - var file: File = await FileService.GetFile(fullPath, data[i]); - files.push(file); - } - - } - } - resolve(files); - }); - - }); + try { + await fs.writeFile(filePath, data); + return true; + } catch (err) { + console.error(err); + return false; + } + } + + public static async ListFiles( + directory: string, + depth: number, + fullDepth: number, + fileExtensions: string[] + ): Promise { + const fileList = await fs.readdir(directory); + + const files: File[] = []; + for (const fileName of fileList) { + const fullPath: string = directory.concat(fileName); + if (await FileService.IsDirectory(fullPath)) { + if (depth < fullDepth) { + for (const element of await FileService.ListFiles( + fullPath + "/", + depth + 1, + fullDepth, + fileExtensions + )) { + files.push(element); + } + } + } else { + const hasExtension: boolean = fullPath.lastIndexOf(".") > 0; + let allowedFile: boolean = false; + if (hasExtension) { + const extension: string = fullPath + .substr(fullPath.lastIndexOf(".") + 1, fullPath.length) + .toLowerCase(); + allowedFile = fileExtensions.filter(m => m === extension).length > 0; + } else { + allowedFile = fileExtensions.filter(m => m === "").length > 0; + } + + if (allowedFile) { + const file: File = await FileService.GetFile(fullPath, fileName); + files.push(file); + } + } } - public static CreateDirTree(userFolder: string, fileName: string): Promise { - let me: FileService = this; - let fullPath: string = userFolder; - - return new Promise(async (resolve, reject) => { - if (fileName.indexOf("|") > -1) { - - let paths = fileName.split("|"); + return files; + } - for (var i = 0; i < paths.length - 1; i++) { - var element = paths[i]; - fullPath += element + "/"; - let x = await FileService.CreateDirectory(fullPath); + public static async CreateDirTree( + userFolder: string, + fileName: string + ): Promise { + let fullPath: string = userFolder; + let result: string; - } - console.log(fullPath + paths[paths.length - 1]); + if (fileName.indexOf("|") > -1) { + const paths: string[] = fileName.split("|"); - resolve(fullPath + paths[paths.length - 1]); - } - else { - console.log(fullPath + fileName); + for (let i = 0; i < paths.length - 1; i++) { + const element = paths[i]; + fullPath += element + "/"; + await FileService.CreateDirectory(fullPath); + } - resolve(fullPath + fileName); - } + result = fullPath + paths[paths.length - 1]; + return result; + } else { + result = fullPath + fileName; - }); + return result; } - - - public static async DeleteFile(filePath: string): Promise { - return new Promise(async resolve => { - if (filePath) { - let stat: boolean = await FileService.FileExists(filePath) - if (stat) { - fs.unlink(filePath, err => { - if (err) resolve(false); - else resolve(true); - }); - } - } - else { - console.error("Unable to delete file. File Path is :" + filePath); - resolve(false); - } - }); + } + + public static async DeleteFile(filePath: string): Promise { + try { + const stat: boolean = await FileService.FileExists(filePath); + if (stat) { + await fs.unlink(filePath); + } + return true; + } catch (err) { + console.error("Unable to delete file. File Path is :" + filePath); + return false; } - - public static async FileExists(filePath: string): Promise { - return new Promise(async resolve => { - fs.access(filePath, fs.F_OK, err => { - if (err) resolve(false); - else resolve(true); - }); - }); + } + + public static async FileExists(filePath: string): Promise { + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + return false; } - - - public static CreateDirectory(name: string): Promise { - return new Promise(async (resolve, reject) => { - fs.mkdir(name, err => { - if (err) reject(err); - else resolve(); - }); - }).then(() => true, err => { - if (err.code == "EEXIST") return false; - else throw err; - }); + } + + public static async CreateDirectory(name: string): Promise { + try { + await fs.mkdir(name); + return true; + } catch (err) { + if (err.code === "EEXIST") { + return false; + } + throw err; } -} \ No newline at end of file + } +} diff --git a/src/service/githubService.ts b/src/service/githubService.ts index d84e2fbc..6eca12ea 100644 --- a/src/service/githubService.ts +++ b/src/service/githubService.ts @@ -1,212 +1,157 @@ "use strict"; -import * as envir from '../environmentPath'; -import {File, FileService} from './fileService'; -import * as vscode from 'vscode'; -import { Environment } from '../environmentPath'; - -var proxyURL: string = vscode.workspace.getConfiguration("http")["proxy"] || process.env["http_proxy"]; -var host: string = vscode.workspace.getConfiguration("sync")["host"]; -var pathPrefix: string = vscode.workspace.getConfiguration("sync")["pathPrefix"]; -if (!host || host === "") { - host = "api.github.com"; - pathPrefix = ""; +import * as GitHubApi from "@octokit/rest"; +import * as HttpsProxyAgent from "https-proxy-agent"; +import * as vscode from "vscode"; +import { File } from "./fileService"; + +interface IEnv { + [key: string]: string | undefined; + http_proxy: string; + HTTP_PROXY: string; } -var GitHubApi = require("github"); -var github = new GitHubApi({ - proxy: proxyURL, - version: "3.0.0", - host: host, - pathPrefix: pathPrefix, - rejectUnauthorized: false -}); export class GitHubService { + public userName: string = null; + public name: string = null; + private github: GitHubApi = null; + private GIST_JSON_EMPTY: GitHubApi.GistsCreateParams = { + description: "Visual Studio Code Sync Settings Gist", + public: false, + files: { + "settings.json": { + content: "// Empty" + }, + "launch.json": { + content: "// Empty" + }, + "keybindings.json": { + content: "// Empty" + }, + "extensions.json": { + content: "// Empty" + }, + "locale.json": { + content: "// Empty" + }, + "keybindingsMac.json": { + content: "// Empty" + }, + cloudSettings: { + content: "// Empty" + } + } + }; - private GIST_JSON_EMPTY: any = { - "description": "Visual Studio Code Sync Settings Gist", - "public": false, - "files": { - "settings.json": { - "content": "// Empty" - }, - "launch.json": { - "content": "// Empty" - }, - "keybindings.json": { - "content": "// Empty" - }, - "extensions.json": { - "content": "// Empty" - }, - "locale.json": { - "content": "// Empty" - }, - "keybindingsMac.json": { - "content": "// Empty" - }, - "cloudSettings": { - "content": "// Empty" - } - } + constructor(userToken: string, basePath: string) { + const githubApiConfig: GitHubApi.Options = { + headers: { + rejectUnauthorized: false + } }; - public userName: string = null; - public name: string = null; - - private GIST_JSON: any = null; - - constructor(private TOKEN: string) { - if (TOKEN != null && TOKEN != '') { - try { - var self: GitHubService = this; - github.authenticate({ - type: "oauth", - token: TOKEN - }); - } catch (error) { - - } - - github.users.get({}, function (err, res) { - if (err) { - console.log(err); - } - else { - self.userName = res.data.login; - self.name = res.data.name; - console.log("Sync : Connected with user : " + "'" + self.userName + "'"); - } - }); - } - } - public AddFile(list: Array, GIST_JSON_b: any) { - for (var i = 0; i < list.length; i++) { - var file = list[i]; - if (file.content != '') { - GIST_JSON_b.files[file.gistName] = {}; - GIST_JSON_b.files[file.gistName].content = file.content; - } - } - return GIST_JSON_b; + const proxyURL: string = + vscode.workspace.getConfiguration("http").get("proxy") || + (process.env as IEnv).http_proxy || + (process.env as IEnv).HTTP_PROXY; + if (basePath) { + githubApiConfig.baseUrl = basePath; } - public CreateEmptyGIST(publicGist: boolean, gistDesciption: string): Promise { - var me = this; - if (publicGist) { - me.GIST_JSON_EMPTY.public = true; - } - else { - me.GIST_JSON_EMPTY.public = false; - } - if (gistDesciption != null && gistDesciption != "") { - me.GIST_JSON_EMPTY.description = gistDesciption; - } - - return new Promise((resolve, reject) => { - github.getGistsApi().create(me.GIST_JSON_EMPTY - , function (err, res) { - if (err) { - console.error(err); - reject(err); - } - else { - if (res.data.id) { - resolve(res.data.id); - } else { - console.error("ID is null"); - console.log("Sync : " + "Response from GitHub is: "); - console.log(res); - } - } - }); - }); + if (proxyURL) { + githubApiConfig.agent = new HttpsProxyAgent(proxyURL); } + this.github = new GitHubApi(githubApiConfig); - public async CreateAnonymousGist(publicGist: boolean, files: Array, gistDesciption: string): Promise { - var me = this; - if (publicGist) { - me.GIST_JSON_EMPTY.public = true; - } - else { - me.GIST_JSON_EMPTY.public = false; - } - if (gistDesciption != null && gistDesciption != "") { - me.GIST_JSON_EMPTY.description = gistDesciption; - } - - let gist: any = me.AddFile(files, me.GIST_JSON_EMPTY); - - return new Promise((resolve, reject) => { - github.getGistsApi().create(gist - , function (err, res) { - if (err) { - console.error(err); - reject(err); - } - if (res.data.id) { - resolve(res.data.id); - } else { - console.error("ID is null"); - console.log("Sync : " + "Response from GitHub is: "); - console.log(res); - } - - }); + if (userToken !== null && userToken !== "") { + try { + this.github.authenticate({ + type: "oauth", + token: userToken }); - } - - - public async ReadGist(GIST: string): Promise { - var me = this; - return new Promise(async (resolve, reject) => { - await github.getGistsApi().get({ id: GIST }, async function (er, res) { - if (er) { - console.error(er); - reject(er); - } - resolve(res); - }); + } catch (err) { + console.error(err); + } + + this.github.users + .get({}) + .then(res => { + this.userName = res.data.login; + this.name = res.data.name; + console.log( + "Sync : Connected with user : " + "'" + this.userName + "'" + ); + }) + .catch(err => { + console.error(err); }); } + } + + public AddFile(list: File[], GIST_JSON_B: any) { + for (const file of list) { + if (file.content !== "") { + GIST_JSON_B.files[file.gistName] = {}; + GIST_JSON_B.files[file.gistName].content = file.content; + } + } + return GIST_JSON_B; + } + + public async CreateEmptyGIST( + publicGist: boolean, + gistDescription: string + ): Promise { + if (publicGist) { + this.GIST_JSON_EMPTY.public = true; + } else { + this.GIST_JSON_EMPTY.public = false; + } + if (gistDescription !== null && gistDescription !== "") { + this.GIST_JSON_EMPTY.description = gistDescription; + } - public UpdateGIST(gistObject: any, files: Array): any { - - var me = this; - var allFiles: string[] = Object.keys(gistObject.data.files); - for (var fileIndex = 0; fileIndex < allFiles.length; fileIndex++) { - var fileName = allFiles[fileIndex]; - - var exists = false; + try { + const res = await this.github.gists.create(this.GIST_JSON_EMPTY); + if (res.data && res.data.id) { + return res.data.id.toString(); + } else { + console.error("ID is null"); + console.log("Sync : " + "Response from GitHub is: "); + console.log(res); + } + } catch (err) { + console.error(err); + throw err; + } + } - files.forEach((settingFile) => { - if (settingFile.gistName == fileName) { - exists = true; - } - }); + public async ReadGist(GIST: string): Promise { + return await this.github.gists.get({ gist_id: GIST }); + } - if (!exists && !fileName.startsWith("keybindings")) { - gistObject.data.files[fileName] = null; - } + public UpdateGIST(gistObject: any, files: File[]): any { + const allFiles: string[] = Object.keys(gistObject.data.files); + for (const fileName of allFiles) { + let exists = false; + for (const settingFile of files) { + if (settingFile.gistName === fileName) { + exists = true; } + } - gistObject.data = me.AddFile(files, gistObject.data); - return gistObject; + if (!exists && !fileName.startsWith("keybindings")) { + gistObject.data.files[fileName] = null; + } } - public async SaveGIST(gistObject: any): Promise { - var me = this; - - //TODO : turn diagnostic mode on for console. - return new Promise(async (resolve, reject) => { - await github.getGistsApi().edit(gistObject, function (ere, ress) { - if (ere) { - console.error(ere); - reject(false); - } - resolve(true); - }); - }); - } + gistObject.data = this.AddFile(files, gistObject.data); + return gistObject; + } + + public async SaveGIST(gistObject: any): Promise { + await this.github.gists.edit(gistObject); + return true; + } } diff --git a/src/service/pluginService.ts b/src/service/pluginService.ts index 94d65828..b1cbe5c8 100644 --- a/src/service/pluginService.ts +++ b/src/service/pluginService.ts @@ -1,379 +1,520 @@ "use strict"; +import * as fs from "fs-extra"; +import * as path from "path"; +import * as vscode from "vscode"; -import * as vscode from 'vscode'; -import * as util from '../util'; -import * as path from 'path'; -import { FileService } from './fileService'; -import { KeyValue } from '../setting'; -const fs = require('fs'); -const ncp = require('ncp').ncp; +import { OsType } from "../enums"; +import * as util from "../util"; -var apiPath = 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery'; +const apiPath = + "https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery"; -const rmdir = require('rimraf'); - -const extensionDir = '.vscode'; +const extensionDir = ".vscode"; export class ExtensionInformation { - metadata: ExtensionMetadata; - name: string; - version: string; - publisher: string; - - public static fromJSON(text: string) { - var obj = JSON.parse(text); - var meta = new ExtensionMetadata(obj.meta.galleryApiUrl, obj.meta.id, obj.meta.downloadUrl, obj.meta.publisherId, obj.meta.publisherDisplayName, obj.meta.date); - var item = new ExtensionInformation(); + public static fromJSON(text: string) { + // TODO: JSON.parse may throw error + // Throw custom error should be more friendly + const obj = JSON.parse(text); + const meta = new ExtensionMetadata( + obj.meta.galleryApiUrl, + obj.meta.id, + obj.meta.downloadUrl, + obj.meta.publisherId, + obj.meta.publisherDisplayName, + obj.meta.date + ); + const item = new ExtensionInformation(); + item.metadata = meta; + item.name = obj.name; + item.publisher = obj.publisher; + item.version = obj.version; + return item; + } + + public static fromJSONList(text: string) { + const extList: ExtensionInformation[] = []; + try { + // TODO: JSON.parse may throw error + // Throw custom error should be more friendly + const list = JSON.parse(text); + list.forEach(obj => { + const meta = new ExtensionMetadata( + obj.metadata.galleryApiUrl, + obj.metadata.id, + obj.metadata.downloadUrl, + obj.metadata.publisherId, + obj.metadata.publisherDisplayName, + obj.metadata.date + ); + const item = new ExtensionInformation(); item.metadata = meta; item.name = obj.name; item.publisher = obj.publisher; item.version = obj.version; - return item; - } - public static fromJSONList(text: string) { - var extList = new Array(); - try { - var list = JSON.parse(text); - list.forEach(obj => { - var meta = new ExtensionMetadata(obj.metadata.galleryApiUrl, obj.metadata.id, obj.metadata.downloadUrl, obj.metadata.publisherId, obj.metadata.publisherDisplayName, obj.metadata.date); - var item = new ExtensionInformation(); - item.metadata = meta; - item.name = obj.name; - item.publisher = obj.publisher; - item.version = obj.version; - - if (item.name != "code-settings-sync") { - extList.push(item); - } - }); + if (item.name !== "code-settings-sync") { + extList.push(item); } - catch (err) { - console.error("Sync : Unable to Parse extensions list",err); - } - - return extList; + }); + } catch (err) { + console.error("Sync : Unable to Parse extensions list", err); } + + return extList; + } + + public metadata: ExtensionMetadata; + public name: string; + public version: string; + public publisher: string; } export class ExtensionMetadata { - galleryApiUrl: string; - id: string; - downloadUrl: string; - publisherId: string; - publisherDisplayName: string; - date: string; - - constructor(galleryApiUrl: string, id: string, downloadUrl: string, publisherId: string, publisherDisplayName: string, date: string) { - this.galleryApiUrl = galleryApiUrl; - this.id = id; - this.downloadUrl = downloadUrl; - this.publisherId = publisherId; - this.publisherDisplayName = publisherDisplayName; - this.date = date; - } + constructor( + public galleryApiUrl: string, + public id: string, + public downloadUrl: string, + public publisherId: string, + public publisherDisplayName: string, + public date: string + ) {} } export class PluginService { + public static GetMissingExtensions( + remoteExt: string, + ignoredExtensions: string[] + ) { + const hashset = {}; + const remoteList = ExtensionInformation.fromJSONList(remoteExt); + const localList = this.CreateExtensionList(); + const missingList: ExtensionInformation[] = []; + + for (const ext of localList) { + if (hashset[ext.name] == null) { + hashset[ext.name] = ext; + } + } - private static CopyExtension(destination: string, source: string) { - return new Promise( - function (resolve, reject) { - ncp(source, destination, function (err) { - if (err) { - reject(err); - } - resolve(); - }) - }); + for (const ext of remoteList) { + if ( + hashset[ext.name] == null && + ignoredExtensions.includes(ext.name) === false + ) { + missingList.push(ext); + } } - private static WritePackageJson(dirName: string, packageJson: string) { - return new Promise( - function (resolve, reject) { - fs.writeFile(dirName + "/extension/package.json", packageJson, "utf-8", function (error, text) { - if (error) { - reject(error); - } - resolve(); - }); - }); + return missingList; + } + + public static GetDeletedExtensions( + remoteList: ExtensionInformation[], + ignoredExtensions: string[] + ) { + const localList = this.CreateExtensionList(); + const deletedList: ExtensionInformation[] = []; + + // for (var i = 0; i < remoteList.length; i++) { + + // var ext = remoteList[i]; + // var found: boolean = false; + + // for (var j = 0; j < localList.length; j++) { + // var localExt = localList[j]; + // if (ext.name == localExt.name) { + // found = true; + // break; + // } + // } + // if (!found) { + // deletedList.push(localExt); + // } + + // } + + for (const ext of localList) { + let found: boolean = false; + if (ext.name !== "code-settings-sync") { + for (const localExt of remoteList) { + if ( + ext.name === localExt.name || + ignoredExtensions.includes(ext.name) + ) { + found = true; + break; + } + } + if (!found) { + deletedList.push(ext); + } + } } - private static GetPackageJson(dirName: string, item: ExtensionInformation) { - return new Promise( - function (resolve, reject) { - fs.readFile(dirName + "/extension/package.json", "utf-8", function (error, text) { - if (error) { - reject(error); - } - var config = JSON.parse(text); - if (config.name !== item.name) { - reject("name not equal"); - } - if (config.publisher !== item.publisher) { - reject("publisher not equal"); - } - if (config.version !== item.version) { - reject("version not equal"); - } - resolve(config); - }); - }); + return deletedList; + } + + public static CreateExtensionList() { + const list: ExtensionInformation[] = []; + + for (const ext of vscode.extensions.all) { + if ( + ext.extensionPath.includes(extensionDir) && // skip if not install from gallery + ext.packageJSON.isBuiltin === false + ) { + const meta = ext.packageJSON.__metadata || { + id: ext.packageJSON.uuid, + publisherId: ext.id, + publisherDisplayName: ext.packageJSON.publisher + }; + const data = new ExtensionMetadata( + meta.galleryApiUrl, + meta.id, + meta.downloadUrl, + meta.publisherId, + meta.publisherDisplayName, + meta.date + ); + const info = new ExtensionInformation(); + info.metadata = data; + info.name = ext.packageJSON.name; + info.publisher = ext.packageJSON.publisher; + info.version = ext.packageJSON.version; + list.push(info); + } } - public static GetMissingExtensions(remoteExt: string) { - var hashset = {}; - var remoteList = ExtensionInformation.fromJSONList(remoteExt); - var localList = this.CreateExtensionList(); - for (var i = 0; i < localList.length; i++) { - var ext = localList[i]; - if (hashset[ext.name] == null) { - hashset[ext.name] = ext; - } - } + return list; + } + + public static async DeleteExtension( + item: ExtensionInformation, + ExtensionFolder: string + ): Promise { + const destination = path.join( + ExtensionFolder, + item.publisher + "." + item.name + "-" + item.version + ); + + try { + await fs.remove(destination); + return true; + } catch (err) { + console.log("Sync : " + "Error in uninstalling Extension."); + console.log(err); + throw err; + } + } + + public static async DeleteExtensions( + extensionsJson: string, + extensionFolder: string, + ignoredExtensions: string[] + ): Promise { + const remoteList = ExtensionInformation.fromJSONList(extensionsJson); + const deletedList = PluginService.GetDeletedExtensions( + remoteList, + ignoredExtensions + ); + const deletedExt: ExtensionInformation[] = []; + + if (deletedList.length === 0) { + return deletedExt; + } + for (const selectedExtension of deletedList) { + try { + await PluginService.DeleteExtension(selectedExtension, extensionFolder); + deletedExt.push(selectedExtension); + } catch (err) { + console.error( + "Sync : Unable to delete extension " + + selectedExtension.name + + " " + + selectedExtension.version + ); + console.error(err); + throw deletedExt; + } + } + return deletedExt; + } + + public static async InstallExtensions( + extensions: string, + extFolder: string, + useCli: boolean, + ignoredExtensions: string[], + osType: OsType, + notificationCallBack: (...data: any[]) => void + ): Promise { + let actionList: Array> = []; + let addedExtensions: ExtensionInformation[] = []; + const missingList = PluginService.GetMissingExtensions( + extensions, + ignoredExtensions + ); + if (missingList.length === 0) { + notificationCallBack("Sync : No Extensions needs to be installed."); + return []; + } - var missingList = new Array(); - for (var i = 0; i < remoteList.length; i++) { - var ext = remoteList[i]; - if (hashset[ext.name] == null) { - missingList.push(ext); + if (useCli) { + addedExtensions = await PluginService.ProcessInstallationCLI( + missingList, + osType, + notificationCallBack + ); + return addedExtensions; + } else { + actionList = await this.ProcessInstallation( + extFolder, + notificationCallBack, + missingList + ); + try { + await Promise.all(actionList); + return addedExtensions; + } catch (err) { + // always return extension list + return addedExtensions; + } + } + } + + public static async ProcessInstallationCLI( + missingList: ExtensionInformation[], + osType: OsType, + notificationCallBack: (...data: any[]) => void + ): Promise { + const addedExtensions: ExtensionInformation[] = []; + const exec = require("child_process").exec; + notificationCallBack("TOTAL EXTENSIONS : " + missingList.length); + notificationCallBack(""); + notificationCallBack(""); + let myExt: string = process.argv0; + console.log(myExt); + let codeLastFolder = "Code"; + if (osType === OsType.Linux) { + codeLastFolder = "code"; + } + if (osType === OsType.Mac) { + codeLastFolder = "Resources/app"; + } + myExt = + '"' + myExt.substr(0, myExt.lastIndexOf(codeLastFolder)) + 'bin//code"'; + for (let i = 0; i < missingList.length; i++) { + const missExt = missingList[i]; + const name = missExt.publisher + "." + missExt.name; + const extensionCli = myExt + " --install-extension " + name; + notificationCallBack(extensionCli); + try { + const installed = await new Promise(res => { + exec(extensionCli, (err, stdout, stderr) => { + if (err || stderr) { + notificationCallBack(err || stderr); + res(false); } + notificationCallBack(stdout); + res(true); + }); + }); + if (installed) { + notificationCallBack(""); + notificationCallBack( + "EXTENSION : " + + (i + 1) + + " / " + + missingList.length.toString() + + " INSTALLED.", + true + ); + notificationCallBack(""); + notificationCallBack(""); + addedExtensions.push(missExt); } - return missingList; + } catch (err) { + console.log(err); + } } - public static GetDeletedExtensions(remoteList: Array) { - - var localList = this.CreateExtensionList(); - var deletedList = new Array(); - - // for (var i = 0; i < remoteList.length; i++) { - - // var ext = remoteList[i]; - // var found: boolean = false; - - // for (var j = 0; j < localList.length; j++) { - // var localExt = localList[j]; - // if (ext.name == localExt.name) { - // found = true; - // break; - // } - // } - // if (!found) { - // deletedList.push(localExt); - // } - - // } - - - for (var i = 0; i < localList.length; i++) { - - var ext = localList[i]; - var found: boolean = false; - if (ext.name != "code-settings-sync") { - for (var j = 0; j < remoteList.length; j++) { - var localExt = remoteList[j]; - - if (ext.name == localExt.name) { - found = true; - break; - } - } - if (!found) { - deletedList.push(ext); - } + return addedExtensions; + } + + public static async ProcessInstallation( + extFolder: string, + notificationCallBack: (...data: any[]) => void, + missingList: ExtensionInformation[] + ) { + const actionList: Array> = []; + const addedExtensions: ExtensionInformation[] = []; + let totalInstalled: number = 0; + for (const element of missingList) { + actionList.push( + PluginService.InstallExtension(element, extFolder).then( + () => { + totalInstalled = totalInstalled + 1; + notificationCallBack( + "Sync : Extension " + + totalInstalled + + " of " + + missingList.length.toString() + + " installed.", + false + ); + addedExtensions.push(element); + }, + (err: any) => { + console.error(err); + notificationCallBack( + "Sync : " + element.name + " Download Failed.", + true + ); + } + ) + ); + } + return actionList; + } + + public static async InstallExtension( + item: ExtensionInformation, + ExtensionFolder: string + ) { + const header = { + Accept: "application/json;api-version=3.0-preview.1" + }; + let extractPath: string = null; + + const data = { + filters: [ + { + criteria: [ + { + filterType: 4, + value: item.metadata.id } + ] } - return deletedList; - } - - public static CreateExtensionList() { - var list = new Array(); - - for (var i = 0; i < vscode.extensions.all.length; i++) { - var ext = vscode.extensions.all[i]; - if (ext.extensionPath.includes(extensionDir) // skip if not install from gallery - && ext.packageJSON.isBuiltin == false - ) { - var meta = ext.packageJSON.__metadata || { - id: ext.packageJSON.uuid, - publisherId: ext.id, - publisherDisplayName: ext.packageJSON.publisher - }; - var data = new ExtensionMetadata(meta.galleryApiUrl, meta.id, meta.downloadUrl, meta.publisherId, meta.publisherDisplayName, meta.date); - var info = new ExtensionInformation(); - info.metadata = data; - info.name = ext.packageJSON.name; - info.publisher = ext.packageJSON.publisher; - info.version = ext.packageJSON.version; - list.push(info); + ], + flags: 133 + }; + + try { + const res = await util.Util.HttpPostJson(apiPath, data, header); + let downloadUrl: string; + + try { + let targetVersion = null; + const content = JSON.parse(res); + + // Find correct version + for (const result of content.results) { + for (const extension of result.extensions) { + for (const version of extension.versions) { + if (version.version === item.version) { + targetVersion = version; + break; + } } + if (targetVersion !== null) { + break; + } + } + if (targetVersion !== null) { + break; + } } - return list; - } + if ( + targetVersion === null || + !targetVersion || + !targetVersion.assetUri + ) { + // unable to find one + throw new Error("NA"); + } - public static async DeleteExtension(item: ExtensionInformation, ExtensionFolder: string): Promise { - var destination = path.join(ExtensionFolder, item.publisher + '.' + item.name + '-' + item.version); - return new Promise((resolve, reject) => { - rmdir(destination, function (error) { - if (error) { - console.log("Sync : " + "Error in uninstalling Extension."); - console.log(error); - reject(false); - } - resolve(true); - }); - }); + // Proceed to install + downloadUrl = + targetVersion.assetUri + + "/Microsoft.VisualStudio.Services.VSIXPackage?install=true"; + console.log("Installing from Url :" + downloadUrl); + } catch (error) { + if (error === "NA" || error.message === "NA") { + console.error( + "Sync : Extension : '" + + item.name + + "' - Version : '" + + item.version + + "' Not Found in marketplace. Remove the extension and upload the settings to fix this." + ); + } + console.error(error); + throw error; + } + + const filePath = await util.Util.HttpGetFile(downloadUrl); + + const dir = await util.Util.Extract(filePath); + + extractPath = dir; + const packageJson = await PluginService.GetPackageJson(dir, item); + + Object.assign(packageJson, { + __metadata: item.metadata + }); + + const text = JSON.stringify(packageJson, null, " "); + await PluginService.WritePackageJson(extractPath, text); + + // Move the folder to correct path + const destination = path.join( + ExtensionFolder, + item.publisher + "." + item.name + "-" + item.version + ); + const source = path.join(extractPath, "extension"); + await PluginService.CopyExtension(destination, source); + } catch (err) { + console.error( + `Sync : Extension : '${item.name}' - Version : '${item.version}'` + err + ); + throw err; } - - public static async DeleteExtensions(extensionsJson: string, extensionFolder: string): Promise> { - return await new Promise>(async (res, rej) => { - var remoteList = ExtensionInformation.fromJSONList(extensionsJson); - var deletedList = PluginService.GetDeletedExtensions(remoteList); - let deletedExt: Array = new Array(); - if (deletedList.length == 0) { - res(deletedExt); - } - for (var deletedItemIndex = 0; deletedItemIndex < deletedList.length; deletedItemIndex++) { - - var selectedExtension = deletedList[deletedItemIndex]; - let extStatus: ExtensionInformation; - try { - let status: boolean = await PluginService.DeleteExtension(selectedExtension, extensionFolder); - deletedExt.push(selectedExtension); - - } catch (err) { - console.error("Sync : Unable to delete extension " + selectedExtension.name + " " + selectedExtension.version); - console.error(err); - rej(deletedExt); - } - } - res(deletedExt); - }); + } + + private static async CopyExtension( + destination: string, + source: string + ): Promise { + await fs.copy(source, destination, { overwrite: true }); + } + private static async WritePackageJson(dirName: string, packageJson: string) { + await fs.writeFile( + dirName + "/extension/package.json", + packageJson, + "utf-8" + ); + } + private static async GetPackageJson( + dirName: string, + item: ExtensionInformation + ) { + const text = await fs.readFile( + dirName + "/extension/package.json", + "utf-8" + ); + + const config = JSON.parse(text); + + if (config.name !== item.name) { + throw new Error("name not equal"); } - - public static async InstallExtensions(extensions: string, extFolder: string, notificationCallBack: Function): Promise> { - return new Promise>(async (res, rej) => { - var missingList = PluginService.GetMissingExtensions(extensions); - if (missingList.length == 0) { - notificationCallBack("Sync : No Extensions needs to be installed."); - res(new Array()); - } - let actionList: Array> = new Array>(); - let addedExtensions: Array = new Array(); - var totalInstalled: number = 0; - await missingList.forEach(async (element, index) => { - (function (ext: ExtensionInformation, folder: string) { - actionList.push(PluginService.InstallExtension(element, extFolder).then(function () { - totalInstalled = totalInstalled + 1; - notificationCallBack("Sync : Extension " + totalInstalled + " of " + missingList.length.toString() + " installed.", false); - addedExtensions.push(element); - }, function (err: any) { - console.error(err); - notificationCallBack("Sync : " + element.name + " Download Failed.", true); - })); - }(element, extFolder)); - - }); - Promise.all(actionList).then(function () { - res(addedExtensions); - }, function () { - rej(addedExtensions); - }); - }); + if (config.publisher !== item.publisher) { + throw new Error("publisher not equal"); } - - public static async InstallExtension(item: ExtensionInformation, ExtensionFolder: string) { - var header = { - 'Accept': 'application/json;api-version=3.0-preview.1' - }; - var extractPath = null; - - var data = { - 'filters': [{ - 'criteria': [{ - 'filterType': 4, - 'value': item.metadata.id - }] - }], - flags: 133 - }; - - return await util.Util.HttpPostJson(apiPath, data, header) - .then(function (res) { - try { - var targetVersion = null; - var content = JSON.parse(res); - - // Find correct version - for (var i = 0; i < content.results.length; i++) { - var result = content.results[i]; - for (var k = 0; k < result.extensions.length; k++) { - var extension = result.extensions[k]; - for (var j = 0; j < extension.versions.length; j++) { - var version = extension.versions[j]; - if (version.version === item.version) { - targetVersion = version; - break; - } - } - if (targetVersion != null) { - break; - } - } - if (targetVersion != null) { - break; - } - } - - if (targetVersion == null || !targetVersion.assetUri) { - // unable to find one - throw "NA"; - } - - // Proceed to install - var downloadUrl = targetVersion.assetUri + '/Microsoft.VisualStudio.Services.VSIXPackage?install=true'; - console.log("Installing from Url :" + downloadUrl); - - return downloadUrl; - } catch (error) { - if (error == "NA") { - console.error("Sync : Extension : '" + item.name + "' - Version : '" + item.version + "' Not Found in marketplace. Remove the extension and upload the settings to fix this."); - } - console.error(error); - // console.log("Response :"); - // console.log(res); - } - - }) - .then(function (url) { - return util.Util.HttpGetFile(url); - }) - .then(function (filePath) { - return util.Util.Extract(filePath); - }) - .then(function (dir) { - extractPath = dir; - return PluginService.GetPackageJson(dir, item); - }) - .then(function (packageJson) { - Object.assign(packageJson, { - __metadata: item.metadata - }); - - var text = JSON.stringify(packageJson, null, ' '); - return PluginService.WritePackageJson(extractPath, text); - }) - .then(function () { - // Move the folder to correct path - var destination = path.join(ExtensionFolder, item.publisher + '.' + item.name + '-' + item.version); - var source = path.join(extractPath, 'extension'); - return PluginService.CopyExtension(destination, source); - }) - .catch(function (error) { - console.error("Sync : Extension : '" + item.name + "' - Version : '" + item.version + "' " + error); - throw error; - }); + if (config.version !== item.version) { + throw new Error("version not equal"); } -} + return config; + } +} diff --git a/src/setting.ts b/src/setting.ts index fb9dde09..c0177d2d 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -1,85 +1,55 @@ -//"use strict"; -import { Environment } from './environmentPath'; - +"use strict"; +import { Environment } from "./environmentPath"; export class ExtensionConfig { - - public gist: string = null; - public host: string = null; - public pathPrefix: string = null; - public quietSync: boolean = false; - public askGistName: boolean = false; - public removeExtensions: boolean = true; - public syncExtensions: boolean = true; - public autoDownload: boolean = false; - public autoUpload = false; - public lastUpload: Date = null; - public lastDownload: Date = null; - public forceDownload: boolean = false; - + public gist: string = null; + public quietSync: boolean = false; + public removeExtensions: boolean = true; + public syncExtensions: boolean = true; + public autoDownload: boolean = false; + public autoUpload = false; + public forceDownload: boolean = false; } export class LocalConfig { - public publicGist: boolean = false; - public userName: string = null; - public name: string = null; - public extConfig: ExtensionConfig = null; - public customConfig: CustomSettings = null; - - constructor() { - this.extConfig = new ExtensionConfig(); - this.customConfig = new CustomSettings(); - } + public publicGist: boolean = false; + public userName: string = null; + public name: string = null; + public extConfig: ExtensionConfig = new ExtensionConfig(); + public customConfig: CustomSettings = new CustomSettings(); } export class CloudSetting { - public lastUpload: Date = null; - public extensionVersion: string = null; - constructor() { - this.extensionVersion = "v" + Environment.getVersion(); - } + public lastUpload: Date = null; + public extensionVersion: string = "v" + Environment.getVersion(); } -export class KeyValue{ - - constructor(public Key: T, public Value: S) { - - } +export class KeyValue { + constructor(public Key: T, public Value: S) {} } export class CustomSettings { - public ignoreUploadFiles: Array = null; - public ignoreUploadFolders: Array = null; - public ignoreExtensions: Array = null; - public ignoreUploadSettings: Array = null; - public replaceCodeSettings: Object = null; - public gistDescription: string = null; - public version: number = 0; - public token: string = null; - public downloadPublicGist = null; - public supportedFileExtensions: Array = null; - public openTokenLink: boolean = true; - constructor() { - - this.ignoreUploadFiles = new Array(); - this.ignoreUploadFolders = new Array(); - this.ignoreExtensions = new Array(); - this.replaceCodeSettings = new Object(); - this.ignoreUploadSettings = new Array(); - this.supportedFileExtensions = new Array(); - this.ignoreUploadFolders.push("workspaceStorage"); - this.ignoreUploadFiles.push("projects.json"); - this.ignoreUploadFiles.push("projects_cache_vscode.json") - this.ignoreUploadFiles.push("projects_cache_git.json") - this.ignoreUploadFiles.push("projects_cache_svn.json") - this.ignoreUploadFiles.push("gpm_projects.json") - this.ignoreUploadFiles.push("gpm-recentItems.json") - this.gistDescription = "Visual Studio Code Settings Sync Gist"; - this.version = Environment.CURRENT_VERSION; - this.supportedFileExtensions.push("json"); - this.supportedFileExtensions.push("code-snippets"); - this.token = ""; - this.downloadPublicGist = false; - this.openTokenLink = true; - } + public ignoreUploadFiles: string[] = [ + "projects.json", + "projects_cache_vscode.json", + "projects_cache_git.json", + "projects_cache_svn.json", + "gpm_projects.json", + "gpm-recentItems.json" + ]; + public ignoreUploadFolders: string[] = ["workspaceStorage"]; + public ignoreExtensions: string[] = []; + public ignoreUploadSettings: string[] = []; + public replaceCodeSettings: { [key: string]: any } = {}; + public gistDescription: string = "Visual Studio Code Settings Sync Gist"; + public version: number = Environment.CURRENT_VERSION; + public token: string = ""; + public downloadPublicGist: boolean = false; + public supportedFileExtensions: string[] = ["json", "code-snippets"]; + public openTokenLink: boolean = true; + public useCliBaseInstallation: boolean = true; + public lastUpload: Date = null; + public lastDownload: Date = null; + public githubEnterpriseUrl: string = null; + public askGistName: boolean = false; } diff --git a/src/sync.ts b/src/sync.ts new file mode 100644 index 00000000..bbceeafb --- /dev/null +++ b/src/sync.ts @@ -0,0 +1,927 @@ +import * as fs from "fs-extra"; +import * as vscode from "vscode"; + +import Commons from "./commons"; +import { OsType } from "./enums"; +import { Environment } from "./environmentPath"; +import localize from "./localize"; +import * as lockfile from "./lockfile"; +import { File, FileService } from "./service/fileService"; +import { GitHubService } from "./service/githubService"; +import { ExtensionInformation, PluginService } from "./service/pluginService"; +import { + CloudSetting, + CustomSettings, + ExtensionConfig, + LocalConfig +} from "./setting"; + +export class Sync { + constructor(private context: vscode.ExtensionContext) {} + /** + * Run when extension have been activated + */ + public async bootstrap(): Promise { + const env = new Environment(this.context); + const globalCommonService = new Commons(env, this.context); + // if lock file not exist + // then create it + if (!(await FileService.FileExists(env.FILE_SYNC_LOCK))) { + await fs.close(await fs.open(env.FILE_SYNC_LOCK, "w")); + } + + // if is locked; + if (await lockfile.Check(env.FILE_SYNC_LOCK)) { + await lockfile.Unlock(env.FILE_SYNC_LOCK); + } + + await globalCommonService.StartMigrationProcess(); + const startUpSetting = await globalCommonService.GetSettings(); + const startUpCustomSetting = await globalCommonService.GetCustomSettings(); + + if (startUpSetting) { + const tokenAvailable: boolean = + startUpCustomSetting.token != null && startUpCustomSetting.token !== ""; + const gistAvailable: boolean = + startUpSetting.gist != null && startUpSetting.gist !== ""; + + if (gistAvailable === true && startUpSetting.autoDownload === true) { + vscode.commands + .executeCommand("extension.downloadSettings") + .then(() => { + if (startUpSetting.autoUpload && tokenAvailable && gistAvailable) { + return globalCommonService.StartWatch(); + } + }); + } + if (startUpSetting.autoUpload && tokenAvailable && gistAvailable) { + return globalCommonService.StartWatch(); + } + } + } + /** + * Upload setting to github gist + */ + public async upload(): Promise { + const args = arguments; + const env = new Environment(this.context); + const common = new Commons(env, this.context); + let github: GitHubService = null; + let localConfig: LocalConfig = new LocalConfig(); + const allSettingFiles: File[] = []; + let uploadedExtensions: ExtensionInformation[] = []; + const ignoredExtensions: ExtensionInformation[] = []; + const dateNow = new Date(); + common.CloseWatch(); + + try { + localConfig = await common.InitalizeSettings(true, false); + localConfig.publicGist = false; + if (args.length > 0) { + if (args[0] === "publicGIST") { + localConfig.publicGist = true; + } + } + + github = new GitHubService( + localConfig.customConfig.token, + localConfig.customConfig.githubEnterpriseUrl + ); + // ignoreSettings = await common.GetIgnoredSettings(localConfig.customConfig.ignoreUploadSettings); + await startGitProcess(localConfig.extConfig, localConfig.customConfig); + // await common.SetIgnoredSettings(ignoreSettings); + } catch (error) { + Commons.LogException(error, common.ERROR_MESSAGE, true); + return; + } + + async function startGitProcess( + syncSetting: ExtensionConfig, + customSettings: CustomSettings + ) { + vscode.window.setStatusBarMessage( + localize("cmd.updateSettings.info.uploading"), + 2000 + ); + + if (customSettings.downloadPublicGist) { + if (customSettings.token == null || customSettings.token === "") { + vscode.window.showInformationMessage( + localize("cmd.updateSettings.warning.noToken") + ); + + return; + } + } + + customSettings.lastUpload = dateNow; + vscode.window.setStatusBarMessage( + localize("cmd.updateSettings.info.readding"), + 2000 + ); + + // var remoteList = ExtensionInformation.fromJSONList(file.content); + // var deletedList = PluginService.GetDeletedExtensions(uploadedExtensions); + if (syncSetting.syncExtensions) { + uploadedExtensions = PluginService.CreateExtensionList(); + if ( + customSettings.ignoreExtensions && + customSettings.ignoreExtensions.length > 0 + ) { + uploadedExtensions = uploadedExtensions.filter(extension => { + if (customSettings.ignoreExtensions.includes(extension.name)) { + ignoredExtensions.push(extension); + return false; + } + return true; + }); + } + uploadedExtensions.sort((a, b) => a.name.localeCompare(b.name)); + const extensionFileName = env.FILE_EXTENSION_NAME; + const extensionFilePath = env.FILE_EXTENSION; + const extensionFileContent = JSON.stringify( + uploadedExtensions, + undefined, + 2 + ); + const extensionFile: File = new File( + extensionFileName, + extensionFileContent, + extensionFilePath, + extensionFileName + ); + allSettingFiles.push(extensionFile); + } + + let contentFiles: File[] = []; + contentFiles = await FileService.ListFiles( + env.USER_FOLDER, + 0, + 2, + customSettings.supportedFileExtensions + ); + + const customExist: boolean = await FileService.FileExists( + env.FILE_CUSTOMIZEDSETTINGS + ); + if (customExist) { + contentFiles = contentFiles.filter( + contentFile => + contentFile.fileName !== env.FILE_CUSTOMIZEDSETTINGS_NAME + ); + + if (customSettings.ignoreUploadFiles.length > 0) { + contentFiles = contentFiles.filter(contentFile => { + const isMatch: boolean = + customSettings.ignoreUploadFiles.indexOf(contentFile.fileName) === + -1 && contentFile.fileName !== env.FILE_CUSTOMIZEDSETTINGS_NAME; + return isMatch; + }); + } + if (customSettings.ignoreUploadFolders.length > 0) { + contentFiles = contentFiles.filter((contentFile: File) => { + const matchedFolders = customSettings.ignoreUploadFolders.filter( + folder => { + return contentFile.filePath.indexOf(folder) === -1; + } + ); + return matchedFolders.length > 0; + }); + } + } else { + Commons.LogException(null, common.ERROR_MESSAGE, true); + return; + } + + for (const snippetFile of contentFiles) { + if ( + snippetFile.fileName !== env.APP_SUMMARY_NAME && + snippetFile.fileName !== env.FILE_KEYBINDING_MAC + ) { + if (snippetFile.content !== "") { + if (snippetFile.fileName === env.FILE_KEYBINDING_NAME) { + snippetFile.gistName = + env.OsType === OsType.Mac + ? env.FILE_KEYBINDING_MAC + : env.FILE_KEYBINDING_DEFAULT; + } + allSettingFiles.push(snippetFile); + } + } + } + + const extProp: CloudSetting = new CloudSetting(); + extProp.lastUpload = dateNow; + const fileName: string = env.FILE_CLOUDSETTINGS_NAME; + const fileContent: string = JSON.stringify(extProp); + const file: File = new File(fileName, fileContent, "", fileName); + allSettingFiles.push(file); + + let completed: boolean = false; + let newGIST: boolean = false; + try { + if (syncSetting.gist == null || syncSetting.gist === "") { + if (customSettings.askGistName) { + customSettings.gistDescription = await common.AskGistName(); + } + newGIST = true; + const gistID = await github.CreateEmptyGIST( + localConfig.publicGist, + customSettings.gistDescription + ); + if (gistID) { + syncSetting.gist = gistID; + vscode.window.setStatusBarMessage( + localize("cmd.updateSettings.info.newGistCreated"), + 2000 + ); + } else { + vscode.window.showInformationMessage( + localize("cmd.updateSettings.error.newGistCreateFail") + ); + return; + } + } + let gistObj = await github.ReadGist(syncSetting.gist); + if (!gistObj) { + vscode.window.showErrorMessage( + localize("cmd.updateSettings.error.readGistFail", syncSetting.gist) + ); + return; + } + + if (gistObj.data.owner !== null) { + const gistOwnerName: string = gistObj.data.owner.login.trim(); + if (github.userName != null) { + const userName: string = github.userName.trim(); + if (gistOwnerName !== userName) { + Commons.LogException( + null, + "Sync : You cant edit GIST for user : " + + gistObj.data.owner.login, + true, + () => { + console.log("Sync : Current User : " + "'" + userName + "'"); + console.log( + "Sync : Gist Owner User : " + "'" + gistOwnerName + "'" + ); + } + ); + return; + } + } + } + + if (gistObj.public === true) { + localConfig.publicGist = true; + } + + vscode.window.setStatusBarMessage( + localize("cmd.updateSettings.info.uploadingFile"), + 3000 + ); + gistObj = github.UpdateGIST(gistObj, allSettingFiles); + completed = await github.SaveGIST(gistObj.data); + if (!completed) { + vscode.window.showErrorMessage( + localize("cmd.updateSettings.error.gistNotSave") + ); + return; + } + } catch (err) { + Commons.LogException(err, common.ERROR_MESSAGE, true); + return; + } + + if (completed) { + try { + const settingsUpdated = await common.SaveSettings(syncSetting); + const customSettingsUpdated = await common.SetCustomSettings( + customSettings + ); + if (settingsUpdated && customSettingsUpdated) { + if (newGIST) { + vscode.window.showInformationMessage( + localize( + "cmd.updateSettings.info.uploadingDone", + syncSetting.gist + ) + ); + } + + if (localConfig.publicGist) { + vscode.window.showInformationMessage( + localize("cmd.updateSettings.info.shareGist") + ); + } + + if (!syncSetting.quietSync) { + common.ShowSummaryOutput( + true, + allSettingFiles, + null, + uploadedExtensions, + ignoredExtensions, + localConfig + ); + vscode.window.setStatusBarMessage("").dispose(); + } else { + vscode.window.setStatusBarMessage("").dispose(); + vscode.window.setStatusBarMessage( + localize("cmd.updateSettings.info.uploadingSuccess"), + 5000 + ); + } + if (syncSetting.autoUpload) { + common.StartWatch(); + } + } + } catch (err) { + Commons.LogException(err, common.ERROR_MESSAGE, true); + } + } + } + } + /** + * Download setting from github gist + */ + public async download(): Promise { + const env = new Environment(this.context); + const common = new Commons(env, this.context); + let localSettings: LocalConfig = new LocalConfig(); + common.CloseWatch(); + + try { + localSettings = await common.InitalizeSettings(true, true); + await StartDownload(localSettings.extConfig, localSettings.customConfig); + } catch (err) { + Commons.LogException(err, common.ERROR_MESSAGE, true); + return; + } + + async function StartDownload( + syncSetting: ExtensionConfig, + customSettings: CustomSettings + ) { + const github = new GitHubService( + customSettings.token, + customSettings.githubEnterpriseUrl + ); + vscode.window.setStatusBarMessage("").dispose(); + vscode.window.setStatusBarMessage( + localize("cmd.downloadSettings.info.readdingOnline"), + 2000 + ); + + const res = await github.ReadGist(syncSetting.gist); + + if (!res) { + Commons.LogException(res, "Sync : Unable to Read Gist.", true); + return; + } + + let addedExtensions: ExtensionInformation[] = []; + let deletedExtensions: ExtensionInformation[] = []; + const ignoredExtensions: string[] = + customSettings.ignoreExtensions || new Array(); + const updatedFiles: File[] = []; + const actionList: Array> = []; + + if (res.data.public === true) { + localSettings.publicGist = true; + } + const keys = Object.keys(res.data.files); + if (keys.indexOf(env.FILE_CLOUDSETTINGS_NAME) > -1) { + const cloudSettGist: object = JSON.parse( + res.data.files[env.FILE_CLOUDSETTINGS_NAME].content + ); + const cloudSett: CloudSetting = Object.assign( + new CloudSetting(), + cloudSettGist + ); + + const lastUploadStr: string = customSettings.lastUpload + ? customSettings.lastUpload.toString() + : ""; + const lastDownloadStr: string = customSettings.lastDownload + ? customSettings.lastDownload.toString() + : ""; + + let upToDate: boolean = false; + if (lastDownloadStr !== "") { + upToDate = + new Date(lastDownloadStr).getTime() === + new Date(cloudSett.lastUpload).getTime(); + } + + if (lastUploadStr !== "") { + upToDate = + upToDate || + new Date(lastUploadStr).getTime() === + new Date(cloudSett.lastUpload).getTime(); + } + + if (!syncSetting.forceDownload) { + if (upToDate) { + vscode.window.setStatusBarMessage("").dispose(); + vscode.window.setStatusBarMessage( + localize("cmd.downloadSettings.info.gotLatestVersion"), + 5000 + ); + return; + } + } + customSettings.lastDownload = cloudSett.lastUpload; + } + + keys.forEach(gistName => { + if (res.data.files[gistName]) { + if (res.data.files[gistName].content) { + if (gistName.indexOf(".") > -1) { + if ( + env.OsType === OsType.Mac && + gistName === env.FILE_KEYBINDING_DEFAULT + ) { + return; + } + if ( + env.OsType !== OsType.Mac && + gistName === env.FILE_KEYBINDING_MAC + ) { + return; + } + const f: File = new File( + gistName, + res.data.files[gistName].content, + null, + gistName + ); + updatedFiles.push(f); + } + } + } else { + console.log(gistName + " key in response is empty."); + } + }); + + for (const file of updatedFiles) { + let writeFile: boolean = false; + const content: string = file.content; + + if (content !== "") { + if (file.gistName === env.FILE_EXTENSION_NAME) { + if (syncSetting.syncExtensions) { + if (syncSetting.removeExtensions) { + try { + deletedExtensions = await PluginService.DeleteExtensions( + content, + env.ExtensionFolder, + ignoredExtensions + ); + } catch (uncompletedExtensions) { + vscode.window.showErrorMessage( + localize("cmd.downloadSettings.error.removeExtFail") + ); + deletedExtensions = uncompletedExtensions; + } + } + let outputChannel: vscode.OutputChannel = null; + if (!syncSetting.quietSync) { + outputChannel = vscode.window.createOutputChannel( + "Code Settings Sync" + ); + outputChannel.clear(); + outputChannel.appendLine( + `CODE SETTINGS SYNC - COMMAND LINE EXTENSION DOWNLOAD SUMMARY` + ); + outputChannel.appendLine( + `Version: ${Environment.getVersion()}` + ); + outputChannel.appendLine(`--------------------`); + outputChannel.show(); + } + + try { + // TODO: Remove Older installation way in next version. + let useCli = true; + if (customSettings.useCliBaseInstallation) { + const autoUpdate: boolean = vscode.workspace + .getConfiguration("extensions") + .get("autoUpdate"); + useCli = autoUpdate; + } else { + useCli = false; + } + + addedExtensions = await PluginService.InstallExtensions( + content, + env.ExtensionFolder, + useCli, + ignoredExtensions, + env.OsType, + (message: string, dispose: boolean) => { + if (!syncSetting.quietSync) { + outputChannel.appendLine(message); + } else { + console.log(message); + if (dispose) { + vscode.window.setStatusBarMessage( + "Sync : " + message, + 3000 + ); + } + } + } + ); + if (!syncSetting.quietSync) { + outputChannel.clear(); + outputChannel.dispose(); + } + } catch (extensions) { + addedExtensions = extensions; + if (!syncSetting.quietSync) { + outputChannel.clear(); + outputChannel.dispose(); + } + } + } + } else { + writeFile = true; + if ( + file.gistName === env.FILE_KEYBINDING_DEFAULT || + file.gistName === env.FILE_KEYBINDING_MAC + ) { + let test: string = ""; + env.OsType === OsType.Mac + ? (test = env.FILE_KEYBINDING_MAC) + : (test = env.FILE_KEYBINDING_DEFAULT); + if (file.gistName !== test) { + writeFile = false; + } + } + if (writeFile) { + if (file.gistName === env.FILE_KEYBINDING_MAC) { + file.fileName = env.FILE_KEYBINDING_DEFAULT; + } + const filePath: string = await FileService.CreateDirTree( + env.USER_FOLDER, + file.fileName + ); + + actionList.push( + FileService.WriteFile(filePath, content) + .then(() => { + // TODO : add Name attribute in File and show information message here with name , when required. + }) + .catch(err => { + Commons.LogException(err, common.ERROR_MESSAGE, true); + return; + }) + ); + } + } + } + } + + await Promise.all(actionList); + const settingsUpdated = await common.SaveSettings(syncSetting); + const customSettingsUpdated = await common.SetCustomSettings( + customSettings + ); + if (settingsUpdated && customSettingsUpdated) { + if (!syncSetting.quietSync) { + common.ShowSummaryOutput( + false, + updatedFiles, + deletedExtensions, + addedExtensions, + null, + localSettings + ); + vscode.window.setStatusBarMessage("").dispose(); + } else { + vscode.window.setStatusBarMessage("").dispose(); + vscode.window.setStatusBarMessage( + localize("cmd.downloadSettings.info.downloaded"), + 5000 + ); + } + if (Object.keys(customSettings.replaceCodeSettings).length > 0) { + const config = vscode.workspace.getConfiguration(); + const keysDefined: string[] = Object.keys( + customSettings.replaceCodeSettings + ); + for (const key of keysDefined) { + const value: string = customSettings.replaceCodeSettings[key]; + const c: any = value === "" ? undefined : value; + config.update(key, c, true); + } + } + if (syncSetting.autoUpload) { + common.StartWatch(); + } + } else { + vscode.window.showErrorMessage( + localize("cmd.downloadSettings.error.unableSave") + ); + } + } + } + /** + * Reset the setting to Sync + */ + public async reset(): Promise { + let extSettings: ExtensionConfig = null; + let localSettings: CustomSettings = null; + + vscode.window.setStatusBarMessage( + localize("cmd.resetSettings.info.resetting"), + 2000 + ); + + try { + const env: Environment = new Environment(this.context); + const common: Commons = new Commons(env, this.context); + + extSettings = new ExtensionConfig(); + localSettings = new CustomSettings(); + + const extSaved: boolean = await common.SaveSettings(extSettings); + const customSaved: boolean = await common.SetCustomSettings( + localSettings + ); + const lockExist: boolean = await FileService.FileExists( + env.FILE_SYNC_LOCK + ); + + if (!lockExist) { + fs.closeSync(fs.openSync(env.FILE_SYNC_LOCK, "w")); + } + + // check is sync locking + if (await lockfile.Check(env.FILE_SYNC_LOCK)) { + await lockfile.Unlock(env.FILE_SYNC_LOCK); + } + + if (extSaved && customSaved) { + vscode.window.showInformationMessage( + localize("cmd.resetSettings.info.settingClear") + ); + } + } catch (err) { + Commons.LogException( + err, + "Sync : Unable to clear settings. Error Logged on console. Please open an issue.", + true + ); + } + } + public async how() { + return vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "http://shanalikhan.github.io/2015/12/15/Visual-Studio-Code-Sync-Settings.html" + ) + ); + } + public async advance() { + const env: Environment = new Environment(this.context); + const common: Commons = new Commons(env, this.context); + const setting: ExtensionConfig = await common.GetSettings(); + const customSettings: CustomSettings = await common.GetCustomSettings(); + const localSetting: LocalConfig = new LocalConfig(); + const tokenAvailable: boolean = + customSettings.token != null && customSettings.token !== ""; + const gistAvailable: boolean = setting.gist != null && setting.gist !== ""; + + const items: string[] = [ + "cmd.otherOptions.editLocalSetting", + "cmd.otherOptions.shareSetting", + "cmd.otherOptions.downloadSetting", + "cmd.otherOptions.toggleForceDownload", + "cmd.otherOptions.toggleAutoUpload", + "cmd.otherOptions.toggleAutoDownload", + "cmd.otherOptions.toggleSummaryPage", + "cmd.otherOptions.preserve", + "cmd.otherOptions.joinCommunity", + "cmd.otherOptions.openIssue", + "cmd.otherOptions.releaseNotes" + ].map(localize); + + let selectedItem: number = 0; + let settingChanged: boolean = false; + + const item = await vscode.window.showQuickPick(items); + + // if not pick anyone, do nothing + if (!item) { + return; + } + + const index = items.findIndex(v => v === item); + + const handlerMap = { + 0: async () => { + const file: vscode.Uri = vscode.Uri.file(env.FILE_CUSTOMIZEDSETTINGS); + fs.openSync(file.fsPath, "r"); + const document = await vscode.workspace.openTextDocument(file); + await vscode.window.showTextDocument( + document, + vscode.ViewColumn.One, + true + ); + }, + 1: async () => { + // share public gist + const answer = await vscode.window.showInformationMessage( + localize("cmd.otherOptions.shareSetting.beforeConfirm"), + "Yes" + ); + + if (answer === "Yes") { + localSetting.publicGist = true; + settingChanged = true; + setting.gist = ""; + selectedItem = 1; + customSettings.downloadPublicGist = false; + await common.SetCustomSettings(customSettings); + } + }, + 2: async () => { + // Download Settings from Public GIST + selectedItem = 2; + customSettings.downloadPublicGist = true; + settingChanged = true; + await common.SetCustomSettings(customSettings); + }, + 3: async () => { + // toggle force download + selectedItem = 3; + settingChanged = true; + setting.forceDownload = !setting.forceDownload; + }, + 4: async () => { + // toggle auto upload + selectedItem = 4; + settingChanged = true; + setting.autoUpload = !setting.autoUpload; + }, + 5: async () => { + // auto download on startup + selectedItem = 5; + settingChanged = true; + if (!setting) { + vscode.commands.executeCommand("extension.HowSettings"); + return; + } + if (!gistAvailable) { + vscode.commands.executeCommand("extension.HowSettings"); + return; + } + + setting.autoDownload = !setting.autoDownload; + }, + 6: async () => { + // page summary toggle + selectedItem = 6; + settingChanged = true; + + if (!tokenAvailable || !gistAvailable) { + vscode.commands.executeCommand("extension.HowSettings"); + return; + } + setting.quietSync = !setting.quietSync; + }, + 7: async () => { + // preserve + const options: vscode.InputBoxOptions = { + ignoreFocusOut: true, + placeHolder: localize("cmd.otherOptions.preserve.placeholder"), + prompt: localize("cmd.otherOptions.preserve.prompt") + }; + const input = await vscode.window.showInputBox(options); + + if (input) { + const settingKey: string = input; + const a = vscode.workspace.getConfiguration(); + const val: string = a.get(settingKey); + customSettings.replaceCodeSettings[input] = val; + const done: boolean = await common.SetCustomSettings(customSettings); + if (done) { + if (val === "") { + vscode.window.showInformationMessage( + localize("cmd.otherOptions.preserve.info.done1", input) + ); + } else { + vscode.window.showInformationMessage( + localize("cmd.otherOptions.preserve.info.done1", input, val) + ); + } + } + } + }, + 8: async () => { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk" + ) + ); + }, + 9: async () => { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://github.com/shanalikhan/code-settings-sync/issues/new" + ) + ); + }, + 10: async () => { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html" + ) + ); + } + }; + + try { + await handlerMap[index](); + if (settingChanged) { + if (selectedItem === 1) { + common.CloseWatch(); + } + await common + .SaveSettings(setting) + .then((added: boolean) => { + if (added) { + const callbackMap = { + 1: async () => { + return await vscode.commands.executeCommand( + "extension.updateSettings", + "publicGIST" + ); + }, + 2: async () => { + return await vscode.window.showInformationMessage( + localize("cmd.otherOptions.warning.tokenNotRequire") + ); + }, + 3: async () => { + const message = setting.forceDownload + ? "cmd.otherOptions.toggleForceDownload.on" + : "cmd.otherOptions.toggleForceDownload.off"; + return vscode.window.showInformationMessage( + localize(message) + ); + }, + 4: async () => { + const message = setting.autoUpload + ? "cmd.otherOptions.toggleAutoUpload.on" + : "cmd.otherOptions.toggleAutoUpload.off"; + return vscode.window.showInformationMessage( + localize(message) + ); + }, + 5: async () => { + const message = setting.autoDownload + ? "cmd.otherOptions.toggleAutoDownload.on" + : "cmd.otherOptions.toggleAutoDownload.off"; + return vscode.window.showInformationMessage( + localize(message) + ); + }, + 6: async () => { + const message = setting.quietSync + ? "cmd.otherOptions.quietSync.on" + : "cmd.otherOptions.quietSync.off"; + return vscode.window.showInformationMessage( + localize(message) + ); + } + }; + + if (callbackMap[selectedItem]) { + return callbackMap[selectedItem](); + } + } else { + return vscode.window.showErrorMessage( + localize("cmd.otherOptions.error.toggleFail") + ); + } + }) + .catch(err => { + Commons.LogException( + err, + "Sync : Unable to toggle. Please open an issue.", + true + ); + }); + } + } catch (err) { + Commons.LogException(err, "Error", true); + return; + } + } +} diff --git a/src/util.ts b/src/util.ts index ead0ecea..57733ed5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,167 +1,182 @@ "use strict"; -import * as http from 'http'; -import * as https from 'https'; -import * as vscode from 'vscode'; -import * as url from 'url'; -import * as fs from 'fs'; - -const adm_zip = require('adm-zip'); -const temp = require('temp').track(); -const HttpsProxyAgent = require("https-proxy-agent"); -var proxy = vscode.workspace.getConfiguration("http")["proxy"] || process.env["http_proxy"]; -var agent = null; -if (proxy) { - if (proxy != '') { - agent = new HttpsProxyAgent(proxy); - } + +import * as adm_zip from "adm-zip"; +import * as fs from "fs-extra"; +import * as http from "http"; +import * as https from "https"; +import * as HttpsProxyAgent from "https-proxy-agent"; +import * as _temp from "temp"; +import * as url from "url"; +import { promisify } from "util"; +import * as vscode from "vscode"; + +interface IHeaders { + [key: string]: string; } +interface IEnv { + [key: string]: string | undefined; + http_proxy: string; + HTTP_PROXY: string; +} -export class Util { +const temp = _temp.track(); +const HTTP_PROXY: string = + (process.env as IEnv).http_proxy || (process.env as IEnv).HTTP_PROXY; - public static HttpPostJson(path: string, obj: Object, headers: Object) { - return new Promise( - function (resolve, reject) { - var item = url.parse(path); - - var postData = JSON.stringify(obj); - var newHeader = { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(postData) - } - Object.assign(newHeader, headers); - var options: https.RequestOptions = { - host: item.hostname, - path: item.path, - method: 'POST', - headers: newHeader, - - } - if (item.port) { - options.port = +item.port - } - if (agent != null) { - options.agent = agent; - } - - - if (item.protocol.startsWith('https:')) { - - var req = https.request(options, function (res) { - if (res.statusCode !== 200) { - //reject(); - //return; - } - - var result = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - result += chunk; - }); - res.on('end', function () { - resolve(result); - }); - - res.on('error', function (e) { - reject(e); - }); - }); - - req.write(postData); - req.end(); - } else { - var req = http.request(options, function (res) { - var result = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - result += chunk; - }); - res.on('end', function () { - resolve(result); - }); - - res.on('error', function (e) { - reject(e); - }); - }); - req.write(postData); - req.end(); - } - } - ) - } - public static HttpGetFile(path: string): Promise { - var tempFile = temp.path(); - var file = fs.createWriteStream(tempFile); - var item = url.parse(path); - var options: https.RequestOptions = { - host: item.hostname, - path: item.path - } - if(item.port){ - options.port = +item.port; - } - if (agent != null) { - options.agent = agent; - } - return new Promise( - function (resolve, reject) { - if (path.startsWith('https:')) { - https.get(options, function(res) { - - res.pipe(file); - file.on('finish', () => { - file.close(); - resolve(tempFile); - }) - }).on('error', (e) => { - reject(e); - }) - } else { - http.get(options, (res) => { - // return value - res.pipe(file); - file.on('finish', () => { - file.close(); - resolve(tempFile); - }) - }).on('error', (e) => { - reject(e); - }) - } - } - ); - } +const proxy = + vscode.workspace.getConfiguration("http").get("proxy") || HTTP_PROXY; +let agent = null; +if (proxy) { + if (proxy !== "") { + agent = new HttpsProxyAgent(proxy); + } +} - public static WriteToFile(content: Buffer): Promise { - var tempFile = temp.path(); - return new Promise( - function (resolve, reject) { - fs.writeFile(tempFile, content, function (err) { - if (err) { - reject(err); - } - resolve(tempFile); - }); - } - ); +export class Util { + public static HttpPostJson(path: string, obj: any, headers: IHeaders) { + return new Promise((resolve, reject) => { + const item = url.parse(path); + const postData = JSON.stringify(obj); + const newHeader = { + "Content-Length": Buffer.byteLength(postData), + "Content-Type": "application/json", + ...headers + }; + const options: https.RequestOptions = { + host: item.hostname, + path: item.path, + headers: newHeader, + method: "POST" + }; + if (item.port) { + options.port = +item.port; + } + if (agent != null) { + options.agent = agent; + } + + if (item.protocol.startsWith("https:")) { + const req = https.request(options, res => { + if (res.statusCode !== 200) { + // reject(); + // return; + } + + let result = ""; + res.setEncoding("utf8"); + res.on("data", (chunk: Buffer | string) => { + result += chunk; + }); + res.on("end", () => resolve(result)); + + res.on("error", (err: Error) => reject(err)); + }); + + req.write(postData); + req.end(); + } else { + const req = http.request(options, res => { + let result = ""; + res.setEncoding("utf8"); + res.on("data", (chunk: Buffer | string) => { + result += chunk; + }); + res.on("end", () => resolve(result)); + + res.on("error", (err: Error) => reject(err)); + }); + req.write(postData); + req.end(); + } + }); + } + public static HttpGetFile(path: string): Promise { + const tempFile = temp.path(); + const file = fs.createWriteStream(tempFile); + const item = url.parse(path); + const options: https.RequestOptions = { + host: item.hostname, + path: item.path + }; + if (item.port) { + options.port = +item.port; } - - public static Extract(filePath: string) { - var dirName = temp.path(); - var zip = new adm_zip(filePath); - - return new Promise( - function (resolve, reject) { - temp.mkdir(dirName, function (err, dirPath) { - try { - zip.extractAllTo(dirName, /*overwrite*/true); - resolve(dirName); - } catch (e) { - reject(e); - } - }); - } - ); + if (agent != null) { + options.agent = agent; } -} \ No newline at end of file + return new Promise((resolve, reject) => { + if (path.startsWith("https:")) { + https + .get(options, res => { + res.pipe(file); + file.on("finish", () => { + file.close(); + resolve(tempFile); + }); + }) + .on("error", e => { + reject(e); + }); + } else { + http + .get(options, res => { + // return value + res.pipe(file); + file.on("finish", () => { + file.close(); + resolve(tempFile); + }); + }) + .on("error", e => { + reject(e); + }); + } + }); + } + + public static async WriteToFile(content: Buffer): Promise { + const tempFile: string = temp.path(); + await fs.writeFile(tempFile, content); + return tempFile; + } + + public static async Extract(filePath: string) { + const dirName = temp.path(); + const zip = new adm_zip(filePath); + + await promisify(temp.mkdir)(dirName); + + zip.extractAllTo(dirName, /*overwrite*/ true); + + return dirName; + } + + public static async Sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(() => { + resolve(ms); + }, ms); + }) as Promise; + } + /** + * promisify the function + * it will be remove when vscode use node@^8.0 + * @param fn + */ + public static promisify( + fn: (...args: any[]) => any + ): (...whatever: any[]) => Promise { + return function(...argv) { + return new Promise((resolve, reject) => { + fn.call(this, ...argv, (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); + }; + } +} diff --git a/tsconfig.json b/tsconfig.json index 1fed8552..ccc518aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,13 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "lib": [ - "es7" - ], - "sourceMap": true, - "rootDir": "." - }, - "exclude": [ - "node_modules", - ".vscode-test" - ] + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": ["es2015","es2016","es2017", "es2018", "esnext"], + "sourceMap": true, + "rootDir": ".", + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "exclude": ["node_modules", ".vscode-test"] } diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..e37dd33b --- /dev/null +++ b/tslint.json @@ -0,0 +1,29 @@ +{ + "extends": [ + "tslint:recommended", + "tslint-plugin-prettier" + ], + "rules": { + "prefer-for-of": false, + "prettier": [ + true, + { + "singleQuote": false + } + ], + "max-classes-per-file": false, + "object-literal-sort-keys": false, + "trailing-comma": false, + "arrow-parens": false, + "no-console": false, + "object-literal-key-quotes": false, + "max-line-length": { + "options": { + "limit": 260 + } + } + }, + "linterOptions": { + "exclude": [] + } +} \ No newline at end of file