├── DEBUG_SIGN_FILE ├── images └── icon.png ├── .travis.yml ├── .editorconfig ├── .vscodeignore ├── .vscode ├── settings.json ├── launch.json └── cSpell.json ├── .eslintrc.js ├── .npmignore ├── CONTRIBUTING.md ├── utils ├── zhs-to-zht.sh └── setup-i18n.js ├── docs ├── MULTI-ROOT-WORKSPACE.zh-CN.md ├── MULTI-ROOT-WORKSPACE.md ├── ABOUT-LOCAL-SERVER.zh-CN.md └── FILE-LIST.md ├── lib ├── i18n │ ├── README.md │ ├── zh-cn.js │ ├── zh-tw.js │ ├── ru.js │ ├── en.js │ └── es.js ├── Constants.js ├── OutputChannelLog.js ├── index.d.ts ├── vscode.d.ts │ └── FETCH.js ├── GetProxyConfiguration.js ├── StatusBarManager.js ├── VSCodeHelper.js ├── EnvironmentProbe.js ├── Log.js ├── vcs │ └── Git.js ├── UploadObject.js ├── Uploader.js ├── thirdPartyCodes │ └── gitPaths.js └── LocalServer.js ├── TODO.md ├── .gitignore ├── package.json ├── CHANGELOG.md ├── README.md ├── extension.js └── LICENSE /DEBUG_SIGN_FILE: -------------------------------------------------------------------------------- 1 | Keeping this file if you want get debug output and running asset 2 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hangxingliu/vscode-coding-tracker/HEAD/images/icon.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | node_js: 8 | - "6" 9 | - "8" 10 | - "node" # latest stable Node.js release 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 4 5 | indent_style = tab 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{md,markdown}] 10 | trim_trailing_whitespace = false 11 | 12 | [*.{yaml,yml}] 13 | indent_size = 2 14 | indent_style = space 15 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # ".vscode" would not match xx/xx/.vscode 2 | .vscode/** 3 | 4 | lib/vscode.d.ts/*.d.ts 5 | 6 | docs/** 7 | test/** 8 | 9 | TODO.md 10 | 11 | database 12 | 13 | npm-debug.log* 14 | .eslintrc.json 15 | 16 | DEBUG_SIGN_FILE 17 | 18 | # any archives 19 | *.7z 20 | *.zip 21 | *.tgz 22 | *.tar 23 | *.vsix 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "codingTracker.serverURL": "http://localhost:10345/", 3 | "codingTracker.uploadToken": "123456", 4 | "codingTracker.computerId": "ubuntuLaptop", 5 | 6 | "files.exclude": { 7 | "**/.git": true, 8 | "**/.svn": true, 9 | "**/.hg": true, 10 | "**/.DS_Store": true 11 | }, 12 | "eslint.enable": true 13 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | plugins: ["@typescript-eslint"], 5 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 6 | rules: { 7 | "no-useless-escape": "off", 8 | "prefer-const": "warn", 9 | "@typescript-eslint/no-var-requires": "off" 10 | }, 11 | env: { 12 | browser: true, 13 | node: true, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ignore archive files 2 | *.7z 3 | *.tar 4 | *.zip 5 | 6 | # ignore vsix 7 | *.vsix 8 | 9 | # ignore generated i18n files 10 | package.nls.json 11 | package.nls.*.json 12 | 13 | # ignore docs 14 | docs 15 | 16 | # ignore .vscode config 17 | .vscode 18 | 19 | # ignore vscode ts file 20 | lib/vscode.d.ts 21 | 22 | # ignore TODO file 23 | TODO.md 24 | 25 | # ignore database folder 26 | database 27 | 28 | # ignore log files 29 | /npm-debug.log* 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Structure/File description 4 | 5 | [docs/FILE-LIST.md](docs/FILE-LIST.md) 6 | 7 | ## Developing 8 | 9 | 1. Using VS Code. 10 | 2. Installing dependencies included devDependencies `npm install` 11 | 3. Installing `vscode.d.ts` for more intelli-sense `npm run install-vscode-dts` 12 | 4. Coding ... 13 | 14 | ## Internationalization 15 | 16 | > LOCALE reference: [Available Locales](https://code.visualstudio.com/docs/getstarted/locales) 17 | 18 | I18N config files are located in `./lib/i18n/`. 19 | -------------------------------------------------------------------------------- /utils/zhs-to-zht.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | throw() { echo "[-] fatal: $1"; exit 1; } 4 | 5 | # goto project directory 6 | pushd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"; 7 | 8 | # pre-check 9 | [[ -n `which opencc` ]] || throw "opencc is not installed! (sudo apt install opencc)"; 10 | [[ -f ./lib/i18n/zh-cn.js ]] || throw "./lib/i18n/zh-cn.js is not a file!"; 11 | 12 | opencc -i ./lib/i18n/zh-cn.js -c s2twp.json -o ./lib/i18n/zh-tw.js || throw "opencc convert failed!"; 13 | 14 | echo "[+] translated to ./lib/i18n/zh-tw.js"; 15 | -------------------------------------------------------------------------------- /docs/MULTI-ROOT-WORKSPACE.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 关于 VSCode 1.18 加入的多项目工作区特性 2 | 3 | ## 兼容性 4 | 5 | 插件仍然支持1.18以前版本的VSCode 6 | 7 | ## 项目/工作区 8 | 9 | 1. 插件生成的 编程活动记录 不会与 工作区关联, 仍和以前一样与 项目(工作区中的每一个根目录) 关联. 10 | 2. 如果你在一个包含多个项目的工作区内打开了一个不在这些项目内的文件, 那与这个文件相关的编程记录 将会与 工作区**最近一次操作的项目** 关联 11 | 12 | 例如: 13 | 14 | 在工作区`workspace`中编程, 这个工作区包含了两个项目(根目录): `/path/to/a` 和 `/path/to/b` 15 | 16 | 1. 首先, 你查看了`path/to/a/file`, 这时会产生一条记录与 项目 `/path/to/a` 关联. 17 | 2. 然后, 你查看了`path/to/b/file`, 这时会产生一条记录与 项目 `/path/to/b` 关联. 18 | 3. 接着, 你查看了不属于这个工作区的文件`/path/to/c/file`, 这时, 项目`/path/to/b` 是上一次记录的项目 19 | - 所以会产生一条记录与 项目`/path/to/b` 关联. 20 | -------------------------------------------------------------------------------- /lib/i18n/README.md: -------------------------------------------------------------------------------- 1 | # Internationalization (I18N) 2 | 3 | The i18n js files under this directory will be convert to VSCode i18n files in the root directory of this project by script: `utils/setup-i18n.js`. 4 | 5 | So execute `utils/setup-i18n.js` after you modify js files in this directory. 6 | 7 | And the `zh-tw.js` file is generated from `zh-cn.js` via [opencc]. (script: `utils/zhs-to-zht.sh`) 8 | 9 | ## Reference 10 | 11 | ## Español 12 | 13 | - 14 | - 15 | 16 | [opencc]: https://github.com/BYVoid/OpenCC 17 | -------------------------------------------------------------------------------- /lib/Constants.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const isDebugMode = isAnyFileExisted( 7 | path.resolve(__dirname, '..', 'DEBUG_SIGN_FILE'), 8 | path.resolve(__dirname, 'DEBUG_SIGN_FILE'), 9 | ); 10 | 11 | module.exports = { 12 | isDebugMode, 13 | 14 | prefix: 'coding-tracker', 15 | outputChannelName: 'Coding Tracker', 16 | }; 17 | 18 | //#==================================== 19 | //#region module private functions 20 | 21 | function isAnyFileExisted(...files) { 22 | for (const file of files) { 23 | try { 24 | if (fs.existsSync(file)) 25 | return true; 26 | } catch (error) { 27 | continue; 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | //#endregion 34 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - [x] replace `spawn` to `fork` for user who have not installed Node.js (`fork` use `Node.js` from VSCode) 4 | - [ ] Tracking terminal by integrated terminal API 5 | - 6 | - [ ] Optimize tracking focus watching time by new API: 7 | - `onDidChangeTextEditorVisibleRanges` 8 | - 9 | - [ ] Optimize upload flow (remove complex vcs queue logic) 10 | - [ ] Add unit tests and travis-ci 11 | - [x] travis-ci for validating i18n files. 12 | - Reference: 13 | - [x] Update dependency `vscode-coding-tracker-server` to `0.6.0` 14 | -------------------------------------------------------------------------------- /lib/i18n/zh-cn.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cmd: { 3 | showReport: "查看你的编程记录报告", 4 | startLocalServer: "启动本地记录服务器", 5 | stopLocalServer: "关闭本地记录服务器" 6 | }, 7 | cfg: { 8 | uploadToken: "你的记录服务器的上传Token", 9 | serverURL: "你的记录服务器的URL", 10 | computerId: [ 11 | "你本机的标识(用于查看报告的时候分辨不同的计算机)", 12 | "(默认值: 'unknown-${os.platform()}')" 13 | ], 14 | localServerMode: [ 15 | "伴随VSCode启动本地记录服务器", 16 | "(如果你希望保存编程记录到远端服务器, 请设置为false)" 17 | ], 18 | moreThinkingTime: [ 19 | "如果你认为你在编程时需要更多时间思考.", 20 | "(这个值越大, 你在报告页面上看到的统计时间就越多)", 21 | "单位: 毫秒. 取值范围: [-1500, +Infinity)" 22 | ], 23 | showStatus: "在状态栏显示状态信息", 24 | proxy: [ 25 | "用于上传记录的网络代理.", 26 | '默认值: "auto" (将自动从选项 `http.proxy` 和系统环境变量中推断代理信息).', 27 | '这个选项的值也可以是: 一个代理服务器的URL, false 或 "no-proxy"' 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/i18n/zh-tw.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cmd: { 3 | showReport: "檢視你的程式設計記錄報告", 4 | startLocalServer: "啟動本地記錄伺服器", 5 | stopLocalServer: "關閉本地記錄伺服器" 6 | }, 7 | cfg: { 8 | uploadToken: "你的記錄伺服器的上傳Token", 9 | serverURL: "你的記錄伺服器的URL", 10 | computerId: [ 11 | "你本機的標識(用於檢視報告的時候分辨不同的計算機)", 12 | "(預設值: 'unknown-${os.platform()}')" 13 | ], 14 | localServerMode: [ 15 | "伴隨VSCode啟動本地記錄伺服器", 16 | "(如果你希望儲存程式設計記錄到遠端伺服器, 請設定為false)" 17 | ], 18 | moreThinkingTime: [ 19 | "如果你認為你在程式設計時需要更多時間思考.", 20 | "(這個值越大, 你在報告頁面上看到的統計時間就越多)", 21 | "單位: 毫秒. 取值範圍: [-1500, +Infinity)" 22 | ], 23 | showStatus: "在狀態列顯示狀態資訊", 24 | proxy: [ 25 | "用於上傳記錄的網路代理.", 26 | '預設值: "auto" (將自動從選項 `http.proxy` 和系統環境變數中推斷代理資訊).', 27 | '這個選項的值也可以是: 一個代理伺服器的URL, false 或 "no-proxy"' 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/OutputChannelLog.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | 4 | /** 5 | * OutputChannel module 6 | * 1. create a new output channel for coding tracker 7 | * 2. output content / exception 8 | * 3. show output channel 9 | * 4. dispose output channel 10 | */ 11 | 12 | const log = require('./Log'); 13 | const vscode = require('vscode'); 14 | const { outputChannelName } = require('./Constants'); 15 | 16 | /** @type {vscode.OutputChannel} */ 17 | let channel = null; 18 | 19 | let getChannel = () => channel = channel || vscode.window.createOutputChannel(outputChannelName) 20 | 21 | module.exports = { 22 | start: getChannel, 23 | stop: () => channel = void (channel && (channel.hide(), channel.dispose())), 24 | debug: data => (log.debug(data), getChannel().appendLine(data)), 25 | error: error => (log.error(error), getChannel().appendLine(error)), 26 | show: () => getChannel().show() 27 | }; 28 | -------------------------------------------------------------------------------- /docs/MULTI-ROOT-WORKSPACE.md: -------------------------------------------------------------------------------- 1 | # About multi-root workspace released to stable since 1.18 2 | 3 | ## Compatibility 4 | 5 | This extension still can be working on VS Code old version. 6 | 7 | ## Project/Workspace 8 | 9 | 1. The tracking record still relating project information but not workspace information. 10 | 2. If you editing/watching a file outside workspace. A record relating **last active project** will be tracked. 11 | 12 | For example: 13 | 14 | You are coding on `workspace`, and this workspace contained two projects(root-path): `/path/to/a` and `/path/to/b` 15 | 16 | 1. Firstly, you watching file `/path/to/a/file`. this watching record will be related to `/path/to/a`. 17 | 2. Secondly, you watching file `/path/to/b/file`. this watching record will be related to `/path/to/b`. 18 | 3. Then, you watching file `/path/to/c/file` what a file outside workspace. 19 | - This watching record will be related to `/path/to/b` because `/path/to/b` is last active project. 20 | 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceFolder}" ], 11 | "stopOnEntry": false 12 | }, 13 | { 14 | "name": "Launch Tests", 15 | "type": "extensionHost", 16 | "request": "launch", 17 | "runtimeExecutable": "${execPath}", 18 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/test" ], 19 | "stopOnEntry": false 20 | }, 21 | { 22 | "name": "Launch Tracker Server", 23 | "type": "node", 24 | "request": "attach" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | type VCSCacheMap = { 5 | [key: string]: { 6 | cache: string; 7 | expiredTime: number; 8 | }; 9 | } 10 | 11 | type UploadObject = { 12 | version: '4.0'; 13 | token: string; 14 | type: string; 15 | time: string; 16 | long: number; 17 | /** 18 | * File language 19 | */ 20 | lang: string; 21 | /** 22 | * File name 23 | */ 24 | file: string; 25 | /** 26 | * Project name 27 | */ 28 | proj: string; 29 | /** 30 | * Computer ID 31 | */ 32 | pcid: string; 33 | 34 | /** 35 | * Version Control System Information 36 | */ 37 | vcs_type: string; 38 | vcs_repo: string; 39 | vcs_branch: string; 40 | 41 | /** 42 | * Line counts 43 | */ 44 | line: number; 45 | /** 46 | * Character counts 47 | */ 48 | char: number; 49 | /** 50 | * Reserved field 1 51 | */ 52 | r1: string; 53 | /** 54 | * Reserved field 2 55 | */ 56 | r2: string; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /docs/ABOUT-LOCAL-SERVER.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 关于本地记录服务器原理 2 | 3 | 相关配置项: `codingTracker.localServerMode` 下文简称`localServerMode` 4 | 5 | `localServerMode`用于无需记录多台计算机编程记录的情况下使用. 6 | (将一个`vscode-coding-tracker-server`程序伴随VSCode启动) 7 | 8 | 本地记录服务器一台计算机只会有一个, 是随着VSCode窗口打开而启动的. 9 | 如果有多个VSCode窗口打开, 后开来打开的窗口会先调用`ajax/kill`接口杀掉之前的本地记录服务器. 10 | 然后在运行一个新的本地记录服务器. 11 | 12 | 然后如果在多个窗口的情况下, 启动有本地记录服务器的窗口被关闭了(意味着本地记录服务器被关闭了). 13 | 别的窗口就没法继续上传编程记录信息了. 所以此时, 收个发现无法上传记录信息的窗口在`localServerMode==true`的情况下, 14 | 会重新运行一个新的本地记录服务器以保证本地记录服务器的正常延续与运作 15 | 16 | ## 当一个VSCode窗口打开时: 17 | 18 | `localServerMode != true` 忽略掉, 因为用户关闭了本地记录服务器模式, 数据上传到远端, 不归本地程序管理 19 | 20 | 1. 调用`http://127.0.0.1:${port}/ajax/kill`接口杀掉正在运行的本地记录服务器(如果存在) 21 | 2. 创建一个新的本地记录服务器 22 | 23 | ## 当VSCode配置改变时 24 | 25 | 检查当前窗口下时候运行着本地记录服务器, 如果有, 就检查相关配置项目是否改变, 若改变则提示用户重新启动VSCode 26 | 27 | ## 当VSCode关闭时 28 | 29 | 如果存在, 就关闭当前窗口运行着的本地记录服务器 30 | 31 | ## 当插件中的所有上传队列上传完时 32 | 33 | 请求接口`/`来获取当前记录服务器时候为本地记录服务器, 并更新状态栏Local信息 34 | 35 | ## 当插件中出现上传记录失败时(由于网络因素导致时) 36 | 37 | `localServerMode == true` 时, 试图创建运行新的本地记录服务器于当前窗口下(因为可能是别的窗口关闭后导致本地记录服务器被关闭) -------------------------------------------------------------------------------- /lib/i18n/ru.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cmd: { 3 | showReport: "Показать отчет", 4 | startLocalServer: "Запустить локальный сервер", 5 | stopLocalServer: "Остановить локальный сервер" 6 | }, 7 | cfg: { 8 | uploadToken: "Ваш токен", 9 | serverURL: "URL сервера", 10 | computerId: [ 11 | "Id вашего компьютера", 12 | "(стандартное значение: 'unknown-${os.platform()}')" 13 | ], 14 | localServerMode: [ 15 | "Начинать отслеживание локально при запуске VSCode", 16 | "(выключите это, если вы хотите сохранять данные на удаленный сервер" 17 | ], 18 | moreThinkingTime: [ 19 | "Если вы думаете, что время обдумывания во время программирования у вас продолжительное.", 20 | "(Чем больше число вы установите, тем больше число вы получите в отчете)", 21 | "Мера измерений: milliseconds. Диапазон: [-1500, +Infinity)" 22 | ], 23 | showStatus: "Отображение состояния трекера (Загрузка / сервер) в строке состояния", 24 | proxy: [ 25 | "Network proxy for uploading.", 26 | '"auto" is the default value, which use proxy from config `http.proxy` and system environments.', 27 | 'And you can use a proxy URL, false or "no-proxy" as the value' 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/i18n/en.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cmd: { 3 | showReport: "Show your coding activities report", 4 | startLocalServer: "Start tracking server in local", 5 | stopLocalServer: "Stop tracking server started in local" 6 | }, 7 | cfg: { 8 | uploadToken: "Your tracker server upload token", 9 | serverURL: "Your tracker server URL", 10 | computerId: [ 11 | "Your computer id(you can recognize different computer in report)", 12 | "(default value: 'unknown-${os.platform()}')" 13 | ], 14 | localServerMode: [ 15 | "Start tracking in local when VSCode start", 16 | "(set this option to false, if you want to upload tracking data to your remote server)" 17 | ], 18 | moreThinkingTime: [ 19 | "If you believe your thinking time in coding activity is long.", 20 | "(The bigger number you set the longer time you see in report page)", 21 | "Unit: milliseconds. Range: [-1500, +Infinity)" 22 | ], 23 | showStatus: "Display tracker upload/server status on the status bar", 24 | proxy: [ 25 | "Network proxy for uploading.", 26 | '"auto" is the default value, which use proxy from config `http.proxy` and system environments.', 27 | 'And you can use a proxy URL, false or "no-proxy" as the value' 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/vscode.d.ts/FETCH.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | //@ts-check 4 | /* eslint-disable no-console */ 5 | 6 | const FROM = 'https://raw.githubusercontent.com/Microsoft/vscode/master/src/vs/vscode.d.ts'; 7 | 8 | const TARGET = `${__dirname}/vscode.d.ts`; 9 | const TARGET_NAMESPACE = `${__dirname}/vscode_namespace.d.ts`; 10 | 11 | const MODULE_REGEXP = /declare\s+module\s+['"]vscode['"]\s+\{/; 12 | 13 | let http = require('https'), 14 | fs = require('fs'); 15 | 16 | http.get(FROM, response => { 17 | if (response.statusCode != 200) 18 | throw new Error(`Response code is ${response.statusCode} !`); 19 | console.log('start receiving body ...'); 20 | 21 | let tsData = '', 22 | tsNamespaceData = ''; 23 | response.setEncoding('utf8'); 24 | response.on('data', chunk => { tsData += chunk; }); 25 | response.on('end', () => { 26 | tsNamespaceData = tsData.replace(MODULE_REGEXP, 'namespace vscode {'); 27 | if (tsData == tsNamespaceData) 28 | throw new Error(`Could not match ${MODULE_REGEXP.toString()} in ts data!`); 29 | 30 | fs.writeFileSync(TARGET, tsData); 31 | fs.writeFileSync(TARGET_NAMESPACE, tsNamespaceData); 32 | 33 | return console.log('\n success: fetch done!\n'); 34 | }); 35 | }).on('error', err => { 36 | throw err 37 | }); 38 | console.log('start requesting vscode.d.ts ...'); 39 | -------------------------------------------------------------------------------- /lib/i18n/es.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cmd: { 3 | showReport: "Mostrar informe de su actividad de programación", 4 | startLocalServer: "Iniciar el servidor de seguimiento en local", 5 | stopLocalServer: "Detener el servidor de seguimiento local" 6 | }, 7 | cfg: { 8 | uploadToken: "El token para subir datos a su servidor de seguimiento", 9 | serverURL: "La URL de tu servidor", 10 | computerId: [ 11 | "La identificación de tu computadora (Se utiliza para reconocer diferentes computadoras en la página del informe.)", 12 | "(valor por defecto: 'unknown-${os.platform()}')" 13 | ], 14 | localServerMode: [ 15 | "Iniciar el servidor de seguimiento en local cuando se inicia VSCode", 16 | "(Configure esta opción en false, si desea cargar datos de seguimiento en su servidor remoto)" 17 | ], 18 | moreThinkingTime: [ 19 | "Si crees que tu tiempo de pensar en la actividad de codificación es largo.", 20 | "(Cuanto mayor sea el número que establezca, más tiempo verá en la página del informe)", 21 | "Unidad: milisegundos. Distancia: [-1500, +Infinity)" 22 | ], 23 | showStatus: "Mostrar el estado del rastreador en la barra de estado", 24 | proxy: [ 25 | "Proxy de red para subir.", 26 | '"auto" es el valor predeterminado (Significa usar proxy desde config `http.proxy` y entornos de sistema)', 27 | 'Puede configurarlo en una URL de proxy, falso o "no-proxy"' 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/cSpell.json: -------------------------------------------------------------------------------- 1 | // cSpell Settings 2 | { 3 | // Version of the setting file. Always 0.1 4 | "version": "0.1", 5 | // language - current active spelling language 6 | "language": "en", 7 | // words - list of words to be always considered correct 8 | "words": [ 9 | "CHANGELOG", 10 | "charset", 11 | "datetime", 12 | "defaultsettings", 13 | "diagrammatized", 14 | "emojione", 15 | "gitignore", 16 | "hangxingliu", 17 | "inmemory", 18 | "intelli", 19 | "jsconfig", 20 | "pcid", 21 | "PROJ", 22 | "recoding", 23 | "repo", 24 | "retracked", 25 | "Retracking", 26 | "timestamp", 27 | "toplevel", 28 | "typeof", 29 | "uploader", 30 | "USERPROFILE", 31 | "vscodeignore", 32 | "Workspaces", 33 | "YYYYMMDD", 34 | "YYYYMMDDHH" 35 | ], 36 | // flagWords - list of words to be always considered incorrect 37 | // This is useful for offensive words and common spelling errors. 38 | // For example "hte" should be "the" 39 | "flagWords": [ 40 | "hte" 41 | ], 42 | // Specify paths/files to ignore. (Supports Globs) 43 | "ignorePaths": [ 44 | "**/node_modules/**", 45 | "**/vscode-extension/**", 46 | "**/.git/**", 47 | ".vscode", 48 | "typings", 49 | "vscode.d.ts", 50 | "cSpell.json" 51 | ] 52 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore archive files 2 | *.7z 3 | *.tar 4 | *.zip 5 | 6 | # ignore *.vsix (extension archive file) 7 | *.vsix 8 | 9 | # ignore generated i18n files 10 | package.nls.json 11 | package.nls.*.json 12 | 13 | # ignore vscode.d.ts files 14 | lib/vscode.d.ts/*.d.ts 15 | 16 | # ignore database folder 17 | database 18 | 19 | #===== FROM GITIGNORE REPOSITORY ===== 20 | 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | 40 | # nyc test coverage 41 | .nyc_output 42 | 43 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 44 | .grunt 45 | 46 | # Bower dependency directory (https://bower.io/) 47 | bower_components 48 | 49 | # node-waf configuration 50 | .lock-wscript 51 | 52 | # Compiled binary addons (http://nodejs.org/api/addons.html) 53 | build/Release 54 | 55 | # Dependency directories 56 | node_modules/ 57 | jspm_packages/ 58 | 59 | # Typescript v1 declaration files 60 | typings/ 61 | 62 | # Optional npm cache directory 63 | .npm 64 | 65 | # Optional eslint cache 66 | .eslintcache 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | 80 | -------------------------------------------------------------------------------- /lib/GetProxyConfiguration.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | module.exports = { 4 | getProxyConfiguration 5 | }; 6 | 7 | function isNull(obj) { return typeof obj === 'object' && !obj; } 8 | 9 | /** 10 | * return: 11 | * undefined means DON'T set `proxy` for request library. 12 | * false means set `proxy` as false for request library. 13 | * a string means set `proxy` as this string for request library. 14 | * 15 | * @param {string|undefined} cfgHttpProxy 16 | * @param {string|boolean|undefined} cfgCodingTrackerProxy 17 | * @returns {undefined|string|false} 18 | */ 19 | function getProxyConfiguration(cfgHttpProxy, cfgCodingTrackerProxy) { 20 | // normalize `codingTracker.proxy`: 21 | if (typeof cfgCodingTrackerProxy === 'string') { 22 | if (cfgCodingTrackerProxy === '' || cfgCodingTrackerProxy === 'auto') { 23 | cfgCodingTrackerProxy = true; 24 | 25 | } else if ( 26 | cfgCodingTrackerProxy === 'noproxy' || 27 | cfgCodingTrackerProxy === 'no-proxy' || 28 | cfgCodingTrackerProxy === 'no_proxy') { 29 | 30 | cfgCodingTrackerProxy = false; 31 | } 32 | 33 | } else if (typeof cfgCodingTrackerProxy === 'undefined' || isNull(cfgCodingTrackerProxy)) { 34 | cfgCodingTrackerProxy = true; // auto 35 | } 36 | 37 | // merge 38 | if (cfgCodingTrackerProxy === true) { // auto proxy 39 | if (typeof cfgHttpProxy === 'string' && cfgHttpProxy) 40 | return cfgHttpProxy; // use vscode config `http.proxy` 41 | return undefined; // use proxy from system environment variables by request 42 | } 43 | 44 | if (cfgCodingTrackerProxy === false) { // disable proxy 45 | return false; 46 | } 47 | 48 | return cfgCodingTrackerProxy; 49 | } 50 | -------------------------------------------------------------------------------- /docs/FILE-LIST.md: -------------------------------------------------------------------------------- 1 | # File List 2 | 3 | ## Extension source files 4 | 5 | - `extension.js`: The **entry** file of this extension. 6 | - `lib/LocalServer.js`: Local server manager (launch, upload, switch and kill) 7 | - `lib/Log.js`: Console log and VSCode popup message module 8 | - `lib/OutputChannelLog.js`: Output into VSCode OutputChannel module 9 | - `OutputChannel` is a pannel named `OUTPUT` in the bottom of VSCode 10 | - `lib/StatusBarManage.js`: VSCode status bar manager module 11 | - `lib/Uploader.js`: Uploader module 12 | - `lib/UploadObject.js`: A packer module that generate tracking data from orginal data 13 | - `lib/VSCodeHelper.js`: A helper module for simplify VSCode extension API 14 | - `lib/vcs/Git.js`: A Git information getter module 15 | - `lib/thirdPartyCodes/*.js`: Third party sources 16 | - `lib/vscode.d.ts/FETCH.hs`: A script to fetch latest vscode.d.ts 17 | - `lib/vscode.d.ts/*.d.ts`: VSCode extension API definition files 18 | - `lib/index.d.ts`: Typs in the extension definition file 19 | - `utils/setup-i18n.js`: A script for setup VSCode extension i18n files 20 | - `package.json` Extension/NPM description file (defined meta info, extension commands and more ...) 21 | - `package.nls.json` i18n file for package.json 22 | - `package.nls.*.json` i18n files for package.json 23 | 24 | ## Development environment files 25 | 26 | - `.vscodeignore`: Files ignore list for VSCode extension (prevent some files from being packed into distributed extension) 27 | - `.gitignore`: Files ignore list for Git 28 | - `.npmignore`: Files ignore list for NPM 29 | - `.editoconfig`: 30 | - `.eslintrc.json`: ESLint configurations 31 | - `.vscode/*.*` the vscode configurations for this project 32 | - `DEBUG_SIGN_FILE` file to enable debug mode for extension 33 | 34 | ## Resource files 35 | 36 | - `images/icon.png` Extension icon from [emojione](http://emojione.com/) 37 | 38 | ## Documents 39 | 40 | - `docs/*.md` 41 | - `README.md` 42 | - `CONTRIBUTING.md` 43 | - `TODO.md` 44 | - `CHANGELOG.md` 45 | - `LICENSE` 46 | -------------------------------------------------------------------------------- /lib/StatusBarManager.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | 4 | "use strict"; 5 | 6 | const vscode = require("vscode"); 7 | const { isDebugMode } = require('./Constants'); 8 | 9 | const MAIN_TITLE = 'CodingTracker'; 10 | const DEBUG_TITLE = 'CodingTracker(Debug)'; 11 | 12 | let statusBarItem;// = vscode.window.createStatusBarItem(); 13 | let uploadQueue = [], 14 | bindUploadQueueArray = Q => uploadQueue = Q; 15 | 16 | let isLocalServerOn = undefined, 17 | mainStatus = ''; 18 | 19 | function init(enable = true) { 20 | if (statusBarItem && !enable) { 21 | statusBarItem.dispose(); 22 | statusBarItem = null; 23 | } 24 | 25 | if(!statusBarItem && enable) 26 | statusBarItem = vscode.window.createStatusBarItem(); 27 | 28 | if(typeof isLocalServerOn == 'undefined') 29 | isLocalServerOn = false; 30 | mainStatus = ''; 31 | _updateText(); 32 | _updateTooltip(); 33 | } 34 | 35 | let setStatus2Uploading = () => _update(mainStatus = 'Uploading...'), 36 | setStatus2Uploaded = desc => _update(mainStatus = desc || 'Uploaded'), 37 | setStatus2GotNew1 = () => _update(mainStatus = '+1'), 38 | setStatus2Nothing = () => _update(mainStatus = null); 39 | 40 | let setLocalServerOn = () => (isLocalServerOn = true, _update()), 41 | setLocalServerOff = () => (isLocalServerOn = false, _update()); 42 | 43 | function _update() { 44 | if (!statusBarItem) return; 45 | _updateText(); 46 | _updateTooltip(); 47 | } 48 | function _updateText() { 49 | if (!statusBarItem) return; 50 | 51 | let text = isDebugMode 52 | ? `$(bug) ${mainStatus || DEBUG_TITLE}` 53 | : `$(dashboard) ${mainStatus || MAIN_TITLE}`; 54 | if (isLocalServerOn) text += ' $(database) Local'; 55 | text += uploadQueue.length ? ('$(chevron-left) ' + uploadQueue.length) : ''; 56 | statusBarItem.text = text; 57 | statusBarItem.show(); 58 | } 59 | function _updateTooltip() { 60 | if (!statusBarItem) return; 61 | 62 | let qLen = uploadQueue.length; 63 | statusBarItem.tooltip = `${MAIN_TITLE} ${isLocalServerOn ? ' - local server is running' : ''}` + 64 | (qLen ? `(${qLen} ${qLen > 1 ? 'records are' : 'record is'} waiting upload!)` : ''); 65 | statusBarItem.show(); 66 | } 67 | 68 | module.exports = { 69 | init, 70 | bindUploadQueueArray, 71 | setStatus2Uploading, 72 | setStatus2Uploaded, 73 | setStatus2GotNew1, 74 | setStatus2Nothing, 75 | localServer: { 76 | turnOn: setLocalServerOn, 77 | turnOff: setLocalServerOff 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /utils/setup-i18n.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | //@ts-check 4 | /* eslint-disable no-console */ 5 | 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | 9 | const PROJECT_DIR = path.join(__dirname, '..'); 10 | const I18N_DIR = path.join(PROJECT_DIR, 'lib', 'i18n'); 11 | 12 | main(); 13 | function main() { 14 | console.log('[.] loading i18n config files ...'); 15 | const configFiles = fs.readdirSync(I18N_DIR).filter(it => /\.js$/i.test(it)); 16 | const configItems = configFiles.map(it => loadI18NConfig(it)); 17 | const enConfig = configItems.find(it => it.lang === 'en'); 18 | 19 | console.log('[.] validating i18n config files ...'); 20 | configItems.forEach(config => { 21 | const expected = new Set(Object.keys(enConfig.data)); 22 | 23 | Object.keys(config.data).forEach(it => { 24 | if (expected.has(it)) 25 | return expected.delete(it); 26 | fatal(`${it} is invalid in i18n file: ${config.source}`); 27 | }); 28 | 29 | for (const missing of Array.from(expected)) 30 | fatal(`${missing} in i18n file: ${config.source}`); 31 | }); 32 | 33 | console.log('[.] generating i18n target files for VSCode extension ...') 34 | configItems.forEach(it => 35 | fs.writeFileSync(path.join(PROJECT_DIR, it.target), JSON.stringify(it.data, null, '\t'))); 36 | 37 | console.log('[+] setup i18n success!'); 38 | } 39 | 40 | 41 | function loadI18NConfig(configFileName = '') { 42 | try { 43 | const rawData = require(path.join(I18N_DIR, configFileName)); 44 | return { 45 | lang: getConfigLanguage(), 46 | source: configFileName, 47 | target: getTargetFileName(), 48 | data: flatten(rawData), 49 | }; 50 | } catch (error) { 51 | fatal(error); 52 | } 53 | 54 | function flatten(obj, out = {}, prefix = '') { 55 | Object.keys(obj).forEach(key => { 56 | let value = obj[key]; 57 | if (prefix) key = `${prefix}.${key}`; 58 | 59 | if (Array.isArray(value)) // multi-line strings 60 | value = value.join('\n'); 61 | if (typeof value === 'object' && value) 62 | return flatten(value, out, key); 63 | out[key] = value; 64 | }); 65 | return out; 66 | } 67 | function getTargetFileName() { 68 | const lang = getConfigLanguage(); 69 | return lang === 'en' ? 'package.nls.json' : `package.nls.${lang}.json`; 70 | } 71 | function getConfigLanguage() { 72 | return configFileName.replace('.js', '').toLowerCase(); 73 | } 74 | } 75 | 76 | function fatal(reason) { 77 | console.error(`[-] fatal: ${String(reason.stack || reason)}`); 78 | process.exit(1); 79 | } 80 | -------------------------------------------------------------------------------- /lib/VSCodeHelper.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | 4 | "use strict"; 5 | let vscode = require('vscode'); 6 | 7 | let showingErrorMsg = 0; 8 | 9 | /** @type {vscode.StatusBarItem} */ 10 | let statusBarItem = null; 11 | 12 | module.exports = { 13 | getConfig, 14 | cloneTextDocument, cloneUri, 15 | dumpDocument, dumpEditor, 16 | showSingleErrorMsg, setStatusBar, 17 | getWhichProjectDocumentBelongsTo 18 | }; 19 | 20 | function getConfig(name = '') { 21 | return vscode.workspace.getConfiguration(name); 22 | } 23 | 24 | /** @param {vscode.TextDocument} doc */ 25 | function cloneTextDocument(doc) { 26 | if (!doc) 27 | return null; 28 | return Object.assign({}, doc, { uri: cloneUri(doc.uri) }); 29 | } 30 | /** @param {vscode.Uri} uri */ 31 | function cloneUri(uri) { 32 | if (!uri) return null; 33 | return vscode.Uri.parse(uri.toString()); 34 | } 35 | 36 | /** @param {vscode.TextDocument} doc */ 37 | function dumpDocument(doc) { 38 | if (!doc) return 'null'; 39 | //document.fileName == document.uri.fsPath 40 | let str = vscode.workspace.asRelativePath(doc.fileName) + 41 | ` (${doc.languageId})(ver:${doc.version})` + 42 | `(scheme: ${doc.uri?doc.uri.scheme:""}): `; 43 | if (doc.isClosed) str += ' Closed'; 44 | if (doc.isDirty) str += ' Dirty'; 45 | if (doc.isUntitled) str += ' Untitled'; 46 | return str; 47 | } 48 | 49 | /** @param {vscode.TextEditor} editor */ 50 | function dumpEditor(editor) { 51 | if (!editor) return 'null'; 52 | return `Editor: (col:${editor.viewColumn}) ${dumpDocument(editor.document)}`; 53 | } 54 | 55 | function showSingleErrorMsg(error) { 56 | if (!showingErrorMsg) 57 | vscode.window.showErrorMessage(error).then(() => 58 | process.nextTick(() => showingErrorMsg = 0)); 59 | showingErrorMsg = 1; 60 | } 61 | function setStatusBar(text, tooltip) { 62 | if(!statusBarItem) 63 | statusBarItem = vscode.window.createStatusBarItem(); 64 | 65 | statusBarItem.text = text || ''; 66 | statusBarItem.tooltip = tooltip || ''; 67 | if (text) 68 | return statusBarItem.show(); 69 | statusBarItem.dispose(); 70 | statusBarItem = null; 71 | } 72 | 73 | /** 74 | * @param {vscode.TextDocument} document 75 | * @param {string} [defaultProjectPath] 76 | */ 77 | function getWhichProjectDocumentBelongsTo(document, defaultProjectPath) { 78 | // Old version vscode has not getWorkspaceFolder api 79 | if (!vscode.workspace.getWorkspaceFolder) 80 | return defaultProjectPath; 81 | 82 | if (!document || !document.uri) 83 | return defaultProjectPath; 84 | let { uri } = document; 85 | 86 | if (uri.scheme != 'file') 87 | return defaultProjectPath; 88 | 89 | let folder = vscode.workspace.getWorkspaceFolder(uri); 90 | 91 | //this document locate in workspace outside 92 | if (!folder) 93 | return defaultProjectPath; 94 | return folder.uri.fsPath; 95 | } -------------------------------------------------------------------------------- /lib/EnvironmentProbe.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | /* 4 | This module is used for probe exetnsion running environment. 5 | It can detect if modules are existed, i18n files are existed. 6 | And it can also generate diagnose file in the extension directory. 7 | */ 8 | 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | 12 | const rootDir = path.resolve(__dirname, '..'); 13 | const getFilePath = filePath => path.resolve(rootDir, filePath); 14 | const getModulePath = moduleName => path.resolve(rootDir, 'node_modules', moduleName); 15 | 16 | module.exports = { generateDiagnoseLogFile }; 17 | 18 | function generateDiagnoseLogFile() { 19 | try { 20 | let dir = rootDir; 21 | if (!isWritable(dir)) dir = require('os').tmpdir(); 22 | if (!isWritable(dir)) throw new Error(`${dir} is not writable`); 23 | 24 | const log = generateDiagnoseContent(); 25 | fs.writeFileSync(path.resolve(dir, 'diagnose.log'), log); 26 | } catch (error) { 27 | onError(error); 28 | } 29 | } 30 | 31 | function generateDiagnoseContent() { 32 | const vscode = safeRequire('vscode'); 33 | const vscodeEnv = vscode && vscode.env || {}; 34 | const packageJson = getPackageJson(); 35 | const i18nJson = getPackageNLSJson(); 36 | return JSON.stringify({ 37 | vscodeAppName: vscodeEnv.appName, 38 | vscodeAppRoot: vscodeEnv.appRoot, 39 | vscodeLanguage: vscodeEnv.language, 40 | packageJsonOk: !!packageJson, 41 | i18nJsonOk: !!i18nJson, 42 | dependencies: getDependencies(packageJson), 43 | }, null, 2); 44 | } 45 | 46 | 47 | function getDependencies(packageJson) { 48 | try { 49 | const deps = Object.keys(packageJson.dependencies); 50 | return deps.map(name => { 51 | const modulePath = getModulePath(name); 52 | return { 53 | name, 54 | path: modulePath, 55 | ok: !!isModuleExisted(name), 56 | }; 57 | }); 58 | } catch (error) { 59 | onError(error); 60 | return []; 61 | } 62 | } 63 | 64 | function getPackageJson() { 65 | try { 66 | return JSON.parse(fs.readFileSync(getFilePath('package.json'), 'utf8')); 67 | } catch (error) { 68 | onError(error); 69 | return null; 70 | } 71 | } 72 | 73 | function getPackageNLSJson() { 74 | try { 75 | return JSON.parse(fs.readFileSync(getFilePath('package.nls.json'), 'utf8')); 76 | } catch (error) { 77 | onError(error); 78 | return null; 79 | } 80 | } 81 | 82 | function safeRequire(name) { 83 | try { 84 | return require(name); 85 | } catch (error) { 86 | onError(error); 87 | } 88 | } 89 | 90 | function isModuleExisted(name) { 91 | try { 92 | return fs.existsSync(getModulePath(name)); 93 | } catch (error) { 94 | onError(error); 95 | return false; 96 | } 97 | } 98 | 99 | function isWritable(dir) { 100 | try { 101 | fs.accessSync(dir, fs.constants.W_OK); 102 | return true; 103 | } catch (error) { 104 | return false; 105 | } 106 | } 107 | 108 | function onError(error) { 109 | // eslint-disable-next-line no-console 110 | console.error(`EnvironmentProbe:`, error); 111 | } 112 | 113 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-coding-tracker", 3 | "displayName": "Coding Tracker", 4 | "description": "A coding activities tracker(time, file, type)", 5 | "version": "0.8.0", 6 | "license": "GPL-3.0", 7 | "publisher": "hangxingliu", 8 | "author": "hangxingliu", 9 | "icon": "images/icon.png", 10 | "engines": { 11 | "vscode": "^1.1.0" 12 | }, 13 | "categories": [ 14 | "Other" 15 | ], 16 | "activationEvents": [ 17 | "*" 18 | ], 19 | "extensionKind": [ 20 | "ui", 21 | "workspace" 22 | ], 23 | "main": "./extension", 24 | "dependencies": { 25 | "axios": "^0.24.0", 26 | "tree-kill": "^1.2.2", 27 | "uuid": "^8.3.2", 28 | "vscode-coding-tracker-server": "^0.6.0" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^14", 32 | "@types/vscode": "*" 33 | }, 34 | "optionalDependencies": { 35 | "@typescript-eslint/eslint-plugin": "^5.4.0", 36 | "@typescript-eslint/parser": "^5.4.0", 37 | "eslint": "^8.2.0" 38 | }, 39 | "contributes": { 40 | "configuration": { 41 | "type": "object", 42 | "title": "Coding Tracker configuration", 43 | "properties": { 44 | "codingTracker.uploadToken": { 45 | "type": "string", 46 | "default": "", 47 | "description": "%cfg.uploadToken%" 48 | }, 49 | "codingTracker.serverURL": { 50 | "type": "string", 51 | "default": "http://127.0.0.1:10345", 52 | "description": "%cfg.serverURL%" 53 | }, 54 | "codingTracker.computerId": { 55 | "type": "string", 56 | "default": "", 57 | "description": "%cfg.computerId%" 58 | }, 59 | "codingTracker.localServerMode": { 60 | "type": "boolean", 61 | "default": true, 62 | "description": "%cfg.localServerMode%" 63 | }, 64 | "codingTracker.moreThinkingTime": { 65 | "type": "number", 66 | "default": 0, 67 | "description": "%cfg.moreThinkingTime%" 68 | }, 69 | "codingTracker.showStatus": { 70 | "type": "boolean", 71 | "default": true, 72 | "description": "%cfg.showStatus%" 73 | }, 74 | "codingTracker.proxy": { 75 | "type": [ 76 | "string", 77 | "boolean" 78 | ], 79 | "default": "auto", 80 | "enum": [ 81 | "auto", 82 | "no-proxy", 83 | false 84 | ], 85 | "description": "%cfg.proxy%" 86 | } 87 | } 88 | }, 89 | "commands": [ 90 | { 91 | "command": "codingTracker.showReport", 92 | "title": "%cmd.showReport%", 93 | "category": "CodingTracker" 94 | }, 95 | { 96 | "command": "codingTracker.startLocalServer", 97 | "title": "%cmd.startLocalServer%", 98 | "category": "CodingTracker" 99 | }, 100 | { 101 | "command": "codingTracker.stopLocalServer", 102 | "title": "%cmd.stopLocalServer%", 103 | "category": "CodingTracker" 104 | } 105 | ] 106 | }, 107 | "repository": { 108 | "type": "git", 109 | "url": "https://github.com/hangxingliu/vscode-coding-tracker" 110 | }, 111 | "scripts": { 112 | "install-vscode-dts": "node ./lib/vscode.d.ts/FETCH.js", 113 | "start": "./node_modules/.bin/coding-tracker-server", 114 | "test": "npm run setup-i18n", 115 | "setup-i18n": "node ./utils/setup-i18n.js" 116 | }, 117 | "keywords": [ 118 | "vscode", 119 | "record", 120 | "report", 121 | "multi-root ready" 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /lib/Log.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | /* eslint-disable no-console */ 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const { isDebugMode, prefix } = require('./Constants'); 8 | 9 | /** @type {(...args) => void} */ 10 | const noop = () => { }; 11 | 12 | const logger = { 13 | error: (...msg) => console.error(prefix, ...msg), 14 | warn: (...msg) => console.warn(prefix, ...msg), 15 | debug: noop, 16 | end: noop, 17 | }; 18 | 19 | if (isDebugMode) 20 | setupDebugLogger(); 21 | 22 | module.exports = logger; 23 | 24 | 25 | //#==================================== 26 | //#region module private functions 27 | 28 | /** 29 | * Setup logger to debug mode (show message box, write log to file) 30 | */ 31 | function setupDebugLogger() { 32 | const vscode = require('vscode'); 33 | const logStream = getDebugLogFileStream(); 34 | 35 | logger.error = (...msg) => { 36 | console.error(prefix, ...msg); 37 | vscode.window.showErrorMessage(`${prefix}: ${msg.join(' ')}`); 38 | if (logStream) 39 | logStream.write('-ERROR', ...msg); 40 | }; 41 | logger.warn = (...msg) => { 42 | console.warn(prefix, ...msg); 43 | vscode.window.showWarningMessage(`${prefix}: ${msg.join(' ')}`); 44 | if (logStream) 45 | logStream.write('-WARN', ...msg); 46 | }; 47 | logger.debug = (...msg) => { 48 | console.log(prefix, ...msg); 49 | if (logStream) 50 | logStream.write('', ...msg); 51 | }; 52 | logger.end = () => { 53 | if (logStream) 54 | logStream.end(); 55 | }; 56 | } 57 | 58 | function getDebugLogFileStream() { 59 | const { v4: uuidV4 } = require('uuid'); 60 | 61 | const DEBUG_LOG_DIR = path.join(__dirname, '..', 'logs'); 62 | 63 | try { 64 | if (!fs.existsSync(DEBUG_LOG_DIR)) 65 | fs.mkdirSync(DEBUG_LOG_DIR); 66 | } catch (error) { 67 | logger.error(`create debug log dir (${DEBUG_LOG_DIR}) failed!`, error); 68 | return; 69 | } 70 | const now = new Date(); 71 | const rand = uuidV4(); 72 | const id = rand.slice(rand.length - 12); 73 | const file = padding(now.getFullYear()) + padding(now.getMonth() + 1) + padding(now.getDate()) + 74 | padding(now.getHours()) + '.log'; 75 | 76 | /** @type {fs.WriteStream} */ 77 | let stream; 78 | let streamErrorOccurred = false; 79 | try { 80 | stream = fs.createWriteStream(path.join(DEBUG_LOG_DIR, file), { flags: 'a' }); 81 | stream.on('error', onStreamError); 82 | } catch (error) { 83 | logger.error(`create debug log file stream (${file}) failed!`, error); 84 | return; 85 | } 86 | logger.debug(`created debug log file stream: ${file}`); 87 | return { 88 | /** @param {string} type */ 89 | write: (type, ...data) => { 90 | if (!stream) return; 91 | stream.write(data.map(it => { 92 | if (Buffer.isBuffer(it)) return it.toString(); 93 | if (it && it.stack) return String(it.stack); 94 | return String(it); 95 | }).join('\t').split('\n').map(it => `${id}${type}:\t${it}`).join('\n') + '\n', noop); 96 | }, 97 | end: () => endStream(), 98 | }; 99 | 100 | function padding(num) { 101 | return 10 > num ? `0${num}` : `${num}`; 102 | } 103 | function endStream() { 104 | try { if (stream) stream.end(); } catch (error) { void error; } 105 | stream = undefined; 106 | } 107 | function onStreamError(error) { 108 | if (streamErrorOccurred) return; 109 | streamErrorOccurred = true; 110 | logger.error('debug log file stream error:', error); 111 | } 112 | } 113 | //#endregion 114 | -------------------------------------------------------------------------------- /lib/vcs/Git.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const vscode = require('vscode'); 7 | const log = require('../Log'); 8 | const git = require('../thirdPartyCodes/gitPaths'); 9 | 10 | const CACHE_REPO = 5 * 60 * 1000; //repo cache valid time: 5 minutes 11 | const CACHE_BRANCH = 30 * 1000; //branch cache valid time 30 seconds 12 | 13 | /** 14 | * This cache object could improve extension to avoid querying repository path again 15 | * @type {VCSCacheMap} 16 | */ 17 | let cacheRepo = {}; 18 | /** 19 | * This cache object could improve extension to avoid querying branch again 20 | * @type {VCSCacheMap} 21 | */ 22 | let cacheBranch = {}; 23 | 24 | // _____ _ 25 | // | ____|_ ___ __ ___ _ __| |_ 26 | // | _| \ \/ / '_ \ / _ \| '__| __| 27 | // | |___ > <| |_) | (_) | | | |_ 28 | // |_____/_/\_\ .__/ \___/|_| \__| 29 | // |_| 30 | module.exports = { getVCSInfo }; 31 | 32 | function isWorkspaceRoot(dirPath) { 33 | return path.resolve(vscode.workspace.rootPath) == path.resolve(dirPath); 34 | } 35 | 36 | /** 37 | * @param {VCSCacheMap} cacheMap 38 | * @param {string} key 39 | */ 40 | function queryCache(cacheMap, key) { 41 | if (!key || !cacheMap[key]) return; 42 | let now = Date.now(), it = cacheMap[key]; 43 | if (it.expiredTime < now) { 44 | delete cacheMap[key]; 45 | return; 46 | } 47 | return it; 48 | } 49 | function addCache(cacheMap, key, cache, time) { 50 | // cacheMap[key] = { cache, expiredTime: Date.now() + time }; 51 | } 52 | function addRepoCache(fileName, repoCache) { addCache(cacheRepo, fileName, repoCache, CACHE_REPO); } 53 | function addBranchCache(repoPath, branchCache) { addCache(cacheBranch, repoPath, branchCache, CACHE_BRANCH); } 54 | 55 | 56 | /** @param {string} documentFileName */ 57 | function getRepoPath(documentFileName) { 58 | return new Promise((resolve, reject) => { 59 | let it = queryCache(cacheRepo, documentFileName); 60 | if (it) return it.cache; 61 | git.getGitRepositoryPath(documentFileName) 62 | .then(repoPath => { addRepoCache(documentFileName, repoPath); resolve(repoPath); }) 63 | .catch(reject); 64 | }) 65 | } 66 | /** @param {string} repoPath */ 67 | function getBranch(repoPath) { 68 | return new Promise((resolve, reject) => { 69 | let it = queryCache(cacheBranch, repoPath); 70 | if (it) return it.cache; 71 | git.getGitBranch(repoPath) 72 | .then(branch => { addBranchCache(repoPath, branch); resolve(branch); }) 73 | .catch(reject); 74 | }) 75 | } 76 | 77 | /** 78 | * This function will be always resolved with a vcs string or undefined 79 | * @param {string} documentFileName 80 | * @returns {Promise} 81 | */ 82 | function getVCSInfo(documentFileName) { 83 | const NO_VCS_INFO = Promise.resolve(undefined); 84 | 85 | if (!fs.existsSync(documentFileName)) 86 | return NO_VCS_INFO; 87 | 88 | return getRepoPath(documentFileName).then(repoPath => { 89 | if (!repoPath) 90 | return NO_VCS_INFO; 91 | 92 | return getBranch(repoPath) 93 | .then(branch => encodeVCSInfo(documentFileName, repoPath, branch)); 94 | }).catch(error => { 95 | log.error('get vcs info error:', documentFileName, error); 96 | return NO_VCS_INFO; 97 | }); 98 | } 99 | 100 | function encodeVCSInfo(fileName, repoPath, branch) { 101 | if (!repoPath) return log.debug(`Can not find any vcs info of document: ${fileName}`), null; 102 | if (!branch) return log.warn(`The vcs "${repoPath}" has not branch information!`), null; 103 | repoPath = isWorkspaceRoot(repoPath) ? '' : repoPath; 104 | log.debug(`got vcs info: git(repo: ${repoPath})(branch: ${branch})\n document: ${fileName}`) 105 | return ['git', repoPath, branch]; 106 | } 107 | 108 | 109 | -------------------------------------------------------------------------------- /lib/UploadObject.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | 4 | const vscode = require('vscode'); 5 | const helper = require('./VSCodeHelper'); 6 | const vcsGit = require('./vcs/Git'); 7 | const log = require('./Log'); 8 | 9 | const { isDebugMode } = require('./Constants'); 10 | 11 | const VSCODE_SETTINGS = 'vscode-settings'; 12 | const VSCODE_INTERACTIVE_PLAYGROUND = 'vscode-interactive-playground'; 13 | const UNKNOWN = 'unknown'; 14 | 15 | let lastActiveProject = UNKNOWN; 16 | /** @type {UploadObject} */ 17 | let baseUploadObject = { 18 | version: '4.0', 19 | token: 'This value will be set up in Uploader module', 20 | type: '', 21 | time: '', 22 | long: 0, 23 | lang: '', 24 | file: '', 25 | proj: '', 26 | pcid: '', 27 | 28 | vcs_type: '', 29 | vcs_repo: '', 30 | vcs_branch: '', 31 | 32 | line: 0, 33 | char: 0, 34 | r1: '1', 35 | r2: '' 36 | }; 37 | 38 | module.exports = { init, generateOpen, generateCode }; 39 | 40 | /** 41 | * Initialize upload object basic information 42 | * @param {string} [computerId] 43 | */ 44 | function init(computerId) { 45 | lastActiveProject = vscode.workspace.rootPath || UNKNOWN; 46 | baseUploadObject.pcid = computerId; 47 | } 48 | function generateOpen(activeDocument, time, long) { return generate('open', activeDocument, time, long); } 49 | function generateCode(activeDocument, time, long) { return generate('code', activeDocument, time, long); } 50 | 51 | /** @param {vscode.TextDocument} activeDocument */ 52 | function generate(type, activeDocument, time, long) { 53 | let obj = Object.assign({}, baseUploadObject); 54 | let uri = activeDocument.uri; 55 | let fileName = activeDocument.fileName; 56 | 57 | obj.type = type; 58 | obj.proj = helper.getWhichProjectDocumentBelongsTo(activeDocument, lastActiveProject); 59 | 60 | // Reference 1: 61 | // why set false to second parameter of function `asRelativePath` 62 | // for example: ./src/hello.js under folder named "project1" in workspace 63 | // result of parameter is true (default value): project1/src/hello.js 64 | // result of parameter is false: src/hello.js 65 | // new parameter since VSCode 1.18.0: 66 | // https://code.visualstudio.com/docs/extensionAPI/vscode-api#workspace.asRelativePath 67 | obj.file = uri.scheme; 68 | obj.lang = activeDocument.languageId; 69 | switch (uri.scheme) { 70 | case 'file': 71 | obj.file = vscode.workspace.asRelativePath(uri.fsPath, false); // ^[1] 72 | obj.lang = activeDocument.languageId; 73 | break; 74 | case 'vscode': 75 | if (uri.authority == 'defaultsettings') { 76 | obj.file = VSCODE_SETTINGS; 77 | obj.lang = VSCODE_SETTINGS; 78 | } else if (isDebugMode) { 79 | vscode.window.showInformationMessage(`Unknown authority in vscode scheme: ${uri.toString()}`); 80 | } 81 | break; 82 | case 'walkThroughSnippet': 83 | if (isDebugMode && !uri.path.endsWith('vs_code_editor_walkthrough.md')) 84 | vscode.window.showWarningMessage(`Invalid vscode interactive playground uri: ${uri.toString()}`); 85 | obj.file = VSCODE_INTERACTIVE_PLAYGROUND; 86 | obj.lang = VSCODE_INTERACTIVE_PLAYGROUND; 87 | break; 88 | } 89 | 90 | obj.time = time; 91 | obj.long = long; 92 | obj.line = activeDocument.lineCount; 93 | obj.char = 0; //TODO: getText().length: But it affect extension efficiency 94 | 95 | if (activeDocument.isUntitled) { 96 | //Ignore vcs information 97 | log.debug(`generated upload object(no vcs) (${dumpUploadObject(obj)}): `); 98 | return Promise.resolve(obj); 99 | } 100 | 101 | return vcsGit.getVCSInfo(fileName).then(vcs => { 102 | if (vcs) { 103 | obj.vcs_type = vcs[0]; 104 | obj.vcs_repo = vcs[1]; 105 | obj.vcs_branch = vcs[2]; 106 | } 107 | log.debug(`generated upload object (${dumpUploadObject(obj)}): `); 108 | return obj; 109 | }); 110 | } 111 | 112 | /** @param {UploadObject} obj */ 113 | function dumpUploadObject(obj) { 114 | return `${obj.type} from ${obj.time} long ${obj.long}; ${obj.file}(${obj.lang}; line: ${obj.line}}); ` + 115 | `vcs: ${obj.vcs_type}:${obj.vcs_repo}:${obj.vcs_branch}`; 116 | } 117 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ### 0.6.0 (2018/03/24) 4 | 5 | 1. Upgrade server program (report page) to 0.6.0 6 | - export/download report as CSV 7 | - merge report from different projects 8 | - fix some bug on report page 9 | - more compatible with old browsers and mobile browsers 10 | 2. Optimize for some vscode internal documents. (*Default settings, markdown preview, interactive playground*) 11 | 3. Add Español translations into extension. 12 | 13 | ### 0.5.0 14 | 15 | 0. Support multi-root workspace. 16 | 1. Add VCS(Git) repository and branch information tracking 17 | 2. Add document line counts tracking 18 | 3. Upgrade uploading protocol version to 4.0 19 | 4. Optimize codes 20 | 21 | ### 0.4.2 22 | 23 | 0. Add configuration `showStatus` to controlling visibility of status bar information 24 | 25 | ### 0.4.1 26 | 27 | 0. Add Russian translations. (Include report page) (Thank [Dolgishev Viktor (@vdolgishev)][vdolgishev]) 28 | 1. Update server program to 0.4.1 (**Fixed fatal bug**) 29 | 30 | ### 0.4.0 31 | 32 | 0. Fixed the bug "could not upload error" caused by switching VSCode windows 33 | 1. New report page (support i18n, detailed report, share ...) (vscode-coding-tracker-server => 0.4.0) 34 | 2. Add configuration "moreThinkingTime". Adjust it to make report more accurate for your coding habits 35 | 36 | ### 0.3.2 37 | 38 | 0. more precise in VSCode 1.9.0 (I am crashing because too many things are changed since VSCode 1.9.0) 39 | 40 | ### 0.3.1 41 | 42 | 0. **fixed the local server severe bug.** 43 | **(because vscode install extension would not resolve dependencies and I forgot a dependency)** 44 | 1. fixed the wrong coding time record because some feature since VSCode 1.9.0 45 | 2. fixed could not upload tracking data in non-project context since VSCode 1.9.0 46 | 3. remove some redundant git merge files 47 | 48 | ### 0.3.0 49 | 50 | 0. **Added local server mode. So you could use this extension easily.** 51 | 1. Added i18n support(supported language: en, zh-cn, zh-tw) 52 | 2. Modified status bar module to show more information(local server sign, tooltip and changed icon) 53 | 3. Added Output channel module to output local server log 54 | 55 | ### 0.2.2 56 | 57 | 0. Fixed `npm start` occurs error in Windows. 58 | 59 | ### 0.2.0 60 | 61 | 0. Be sure to upgrade again, because accuracy of tracker has be improve 62 | 1. Separated the server side codes to other repository(but add this server side module to npm package dependencies. 63 | So you can find server side codes under node_modules) 64 | 2. Ignored tracking invalid document times 65 | 3. Added listening onDidChangeTextEditorSelection event to improve accuracy 66 | 4. Tidied extension.js codes 67 | 68 | ### 0.1.5 69 | 70 | 0. Be sure to upgrade, reason be following 71 | 1. Fixed two severe bugs. So you will get your right coding and watching time 72 | 73 | ### 0.1.4 74 | 75 | 0. Add computer Id to tracking data(You can specify your Id by set up vscode config 76 | `codingTracker.computerId` ) 77 | 1. Fixed some spelling mistake in the code and change some comment from Chinese to English 78 | 2. Change tracking data time format from datetime format string to timestamp 79 | 3. Please upgrade your server program to at least 1.3.0 to support receive tracking data 80 | and storage data in version 3.0 81 | 82 | ### 0.1.3 83 | 84 | 0. Modified the log module, removed export log object to global variable "Log" (because sometimes it lead to a vscode exception) 85 | 86 | ### 0.1.2 87 | 88 | 0. Fixed a bug around node module require in Linux must care about character case. 89 | 90 | ### 0.1.1 91 | 92 | 0. Change folder and project structure to adapt npm and vscode extension 93 | 94 | ### 0.1.0 95 | 96 | 0. Add an icon to extension 97 | 1. **Fixed the severe bug** (could not to use this extension because dependencies list is incomplete) 98 | 2. Optimized tracking data upload. 99 | 3. Support upload configurations take effect after change configurations in settings.json without restart VSCode 100 | 4. Upgrade upload object structure version and storage version to 2.0, 101 | remove unnecessary field and change a time(date) field format to avoid time difference between server and client. 102 | 5. Optimized the server script performance and structure. 103 | -------------------------------------------------------------------------------- /lib/Uploader.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | 4 | const vscode = require('vscode'); 5 | const ext = require('./VSCodeHelper'); 6 | const Request = require('request'); 7 | const statusBar = require('./StatusBarManager'); 8 | const localServer = require('./LocalServer'); 9 | const log = require('./Log'); 10 | const { isDebugMode } = require('./Constants'); 11 | 12 | let Q = [], 13 | uploadURL, 14 | uploadToken, 15 | uploadHeader = { 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' }, 16 | uploading = 0, 17 | //Avoid Show error information too many times 18 | hadShowError = 0, 19 | retryTime = 0, 20 | uploadProxy = undefined; 21 | 22 | let uploader = { 23 | init: function () { 24 | statusBar.bindUploadQueueArray(Q); 25 | }, 26 | /** 27 | * @param {string} url 28 | * @param {string} token 29 | * @param {string|boolean|undefined} proxy 30 | */ 31 | set: function (url, token, proxy) { 32 | uploadURL = url; 33 | uploadToken = token; 34 | uploadProxy = proxy; 35 | log.debug('uploader configurations changed!'); 36 | }, 37 | upload: function (data) { 38 | if (!data) 39 | return log.debug(`new upload object(ignored): ${data}`); 40 | log.debug(`new upload object: ${data.type};${data.time};${data.long};${data.file}`); 41 | Q.push(data); 42 | statusBar.setStatus2GotNew1(); 43 | process.nextTick(_upload); 44 | } 45 | } 46 | 47 | function _upload() { 48 | 49 | //Upload Lock 50 | if (uploading) 51 | return; 52 | //Queue is empty 53 | if (!Q[0]) { 54 | //Now the queue is empty 55 | //And check is the server running in the local 56 | // - If in local, status bar will display "Local", Else not 57 | localServer.activeCheckIsLocalServerStart(); 58 | return statusBar.setStatus2Nothing(); 59 | } 60 | 61 | uploading = 1; 62 | 63 | var data = Q[0]; 64 | //Set up upload token 65 | data.token = uploadToken; 66 | 67 | statusBar.setStatus2Uploading(); 68 | 69 | /** @type {Request.CoreOptions} */ 70 | const uploadOptions = { method: 'POST', form: data, headers: uploadHeader }; 71 | if (typeof uploadProxy !== 'undefined') 72 | uploadOptions.proxy = uploadProxy; 73 | 74 | // if (isDebugMode) { 75 | // const dump = JSON.stringify(uploadOptions, null, 2).split('\n').map(it => ` ${it}`); 76 | // log.debug(`Upload options:\n${dump}`); 77 | // } 78 | 79 | Request(uploadURL, uploadOptions, function (err, res, bd) { 80 | uploading = 0; 81 | 82 | let success = true; 83 | let returnObject = {error: 'Unable to connect'}; 84 | 85 | if (err) { 86 | success = false; 87 | //Upload failed because network error 88 | //So check is local server mode ? 89 | //If is local server mode now, just start a new local server now. 90 | localServer.detectOldSever_SoStartANewIfUnderLocalMode() || 91 | showErrorMessage(1, `Could not upload coding record: ${err.stack}`); 92 | } 93 | 94 | //If there are not network error 95 | if (success) { 96 | if (res.statusCode === 200 || res.statusCode === 403) { 97 | returnObject = toJSON(bd); 98 | if (returnObject.JSONError) { 99 | success = false; 100 | showErrorMessage(2, `Upload error: Server response content is not JSON!`); 101 | } 102 | if (returnObject.error) { 103 | success = false; 104 | showErrorMessage(3, `Upload error: ${returnObject.error}`); 105 | } 106 | } else { 107 | success = false; 108 | showErrorMessage(2, `Upload error: Response: ${res.statusCode} (${res.statusMessage})`); 109 | } 110 | } 111 | 112 | //update status bar information 113 | statusBar.setStatus2Uploaded(returnObject.error || null); 114 | 115 | uploading = 0; 116 | //upload failed 117 | if (!success) { 118 | if (retryTime >= 3) 119 | return statusBar.setStatus2Nothing(); 120 | retryTime++; 121 | setTimeout(_upload, 3000); 122 | } else { 123 | Q.shift(); 124 | hadShowError = retryTime = 0; 125 | process.nextTick(_upload); 126 | log.debug('Uploaded success!'); 127 | } 128 | 129 | //End callback 130 | }) 131 | } 132 | 133 | //Methods 134 | function toJSON(bd) { 135 | try { 136 | return JSON.parse(bd); 137 | } catch (err) { 138 | log.error(`Parse JSON failed! (${bd})`); 139 | return { JSONError: true, error: 'Unrecognized response' }; 140 | } 141 | } 142 | 143 | /** 144 | * @param {number} id 145 | * @param {Error|string} error 146 | * @returns 147 | */ 148 | function showErrorMessage(id, error) { 149 | log.error(error); 150 | if (hadShowError == id) 151 | return; 152 | hadShowError = id; 153 | ext.showSingleErrorMsg(error); 154 | } 155 | 156 | module.exports = uploader; 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visual Studio Code Coding Tracker 2 | 3 | **A VSCode extension that track your coding activities and generate report about your coding.** 4 | You can know how much time you spent on each projects/files/computers/languages/branchs and total 5 | by this extension. 6 | 7 | Supported languages: 8 | English, Russian(русский), Spanish(Español), 9 | Simplified Chinese(简体中文) and Traditional Chinese(繁體中文). 10 | 11 | All part of this extension(included server program, documents) are open-source and hosted on Github. 12 | 13 | > Links: 14 | > [Server program Github repo](https://github.com/hangxingliu/vscode-coding-tracker-server) 15 | > [VSCode extensions marketplace](https://marketplace.visualstudio.com/items?itemName=hangxingliu.vscode-coding-tracker) 16 | 17 | ## Screenshot 18 | 19 | ![screenshots2](https://raw.githubusercontent.com/hangxingliu/vscode-coding-tracker-server/master/screenshots/2.jpg) 20 | 21 | ## Current Version 22 | 23 | ### 0.7.0 (Next version) 24 | 25 | 1. Fix local server launch faild on users who have not install `Node.js` or installed `nvm`. 26 | 27 | If you want use this version now, 28 | **you can uninstall old version in VSCode, And clone this repository into `$HOME/.vscode/extensions`** 29 | 30 | ### 0.6.0 (2018/03/25) 31 | 32 | 1. Upgrade server program (report page) to 0.6.0 33 | - export/download report as CSV 34 | - merge report from different projects 35 | - fix some bug on report page 36 | - more compatible with old browsers and mobile browsers 37 | 2. Optimize for some vscode internal documents. (*Default settings, markdown preview, interactive playground*) 38 | 3. Add Español translations into extension. 39 | 40 | more version information: [CHANGELOG.md](CHANGELOG.md) 41 | 42 | ## **How To Use (Easy And Common Way)** 43 | 44 | **Applicable to people dont want to read below long text and only use VSCode in one computer** 45 | 46 | 1. Install this extension. 47 | 2. Coding as you did before. 48 | 3. Get your coding report by command **CodingTracker: Show your coding activities report** 49 | - *press `F1` to open VSCode command panel, then search command above and click it* 50 | 51 | ## How To Use (Fully guide) **TLDR** 52 | 53 | > VSCode Coding Tracker actually has two part: extension and server (C/S) 54 | > 55 | > And extension use internal server installed in *node_modules* by default. 56 | > 57 | > But you could install a server program on you server and use it on VSCode on different computers. 58 | > 59 | > Server program repository: [vscode-coding-tracker-server](https://github.com/hangxingliu/vscode-coding-tracker-server) 60 | 61 | ### Step1. Installing extension to your VSCode 62 | 63 | Search `vscode-coding-tracker` in VSCode extension panel and install it. 64 | 65 | ### Step2. Install and Launching tracker server in remote server or local 66 | 67 | #### Local computer (controlled by VSCode) 68 | 69 | You don't need to do anything.(And **don't** change the configuration `codingTracker.localServerMode` to `false`) 70 | 71 | In this situation, the database files are located in `$HOME/.coding-tracker/` 72 | 73 | #### Local computer (controlled by yourself) 74 | 75 | 0. Set your vscode configuration `codingTracker.localServerMode` to `false` 76 | 1. Open a terminal/command line 77 | 2. Change path to `%HOME%/.vscode/extensions/hangxingliu.vscode-coding-tracker-0.6.0` 78 | - In Windows OS, enter command: `cd %HOME%/.vscode/extensions/hangxingliu.vscode-coding-tracker-0.6.0` 79 | - In Linux/Mac OS, enter command: `cd $HOME/.vscode/extensions/hangxingliu.vscode-coding-tracker-0.6.0` 80 | 3. Execute `npm i` 81 | 4. Launch tracker server by using command: `npm start -- -t ${REPLACE_TO_YOUR_TOKEN}` 82 | - Such as `npm start -- -t test_token`, means your upload token is `test_token` 83 | - And you can get more configurations and descriptions by using command `npm start -- --help` 84 | - Be care! It is necessary to add `--` following to `npm start` to passing following arguments to tracker server 85 | 5. And your tracking data is under `./database` in default. 86 | 87 | #### Remote server 88 | 89 | 0. Set your vscode configuration `codingTracker.localServerMode` to `false` 90 | 1. Login into your remote server 91 | 2. Be sure to install `node` and `npm` environments 92 | 3. Typing command `npm i vscode-coding-tracker-server` (Global install: append ` -g` to the command) 93 | 4. Launch tracker server by using command: `npm start -- -t ${REPLACE_TO_YOUR_TOKEN}` 94 | 5. And your tracking data is under `./database` in default. 95 | 96 | ### Step 3. Configuring the upload token and your server address in your VSCode 97 | 98 | configurations: 99 | 100 | - `codingTracker.serverURL` (set up such as "http://localhost:10345") 101 | - If you use local tracker server and use default config, you can ignore this config. 102 | - Because default value of this config is `http://localhost:10345` 103 | - `codingTracker.uploadToken` (set up such as "123456") 104 | - Setting up this value same as the token you launch your server 105 | - `codingTracker.computerId` (set up this computer name then you can easy to know which computer you coding more time) 106 | - (Optional config) 107 | - `codingTracker.localServerMode` (in default is true). Please refer above 108 | - `codingTracker.moreThinkingTime` (in default is 0 ). More thinking time for tracking 109 | - This config is making for people need more thinking time in coding activity. 110 | - The bigger value you set the longer time you get in report time 111 | - **I don't recommend setting up this value bigger, Because I believe the default think time in extension is nice followed my usage** 112 | 113 | ### Step 4. See your report 114 | 115 | Open command panel in your VSCode.Then search and click command `CodingTracker: Show your coding activities report` 116 | 117 | Or, just open browser and enter `http://${YOUR_SERVER_HOST_NAME}:${PORT}/report/?token=${API_TOKEN}` 118 | 119 | - Such as `http://127.0.0.1:10345/report/` 120 | - Such as `http://mydomain.com:10345/report/?token=myUploadToken` 121 | 122 | ### More commands: 123 | 124 | - `codingTracker.startLocalServer` 125 | - `codingTracker.stopLocalServer` 126 | - `codingTracker.showReport` 127 | 128 | ## Contributing 129 | 130 | [CONTRIBUTING.md](CONTRIBUTING.md) 131 | 132 | ## Author 133 | 134 | [LiuYue (hangxingliu)](https://github.com/hangxingliu) 135 | 136 | ## Contributors 137 | 138 | - [Ted Piotrowski (@ted-piotrowski)][ted-piotrowski] 139 | - [Dolgishev Viktor (@vdolgishev)][vdolgishev] 140 | 141 | ## Third party codes and resource 142 | 143 | - The icon of this extension is from [emojione](http://emojione.com/). This project help me a lot of (bkz I dont know how to use PS and dont have art sense). 144 | - `lib/thirdPartyCodes/gitPaths.js` is modified from 145 | 146 | ## License 147 | 148 | - Extension(excluded icon and third party codes) and server scripts are licensed under [GPL-3.0](LICENSE) 149 | - Icon of extension is licensed under [CC-BY 4.0](http://emojione.com/licensing/) 150 | - Third party codes license information in the front of third party code files 151 | 152 | [vdolgishev]: https://github.com/vdolgishev 153 | [ted-piotrowski]: https://github.com/ted-piotrowski 154 | -------------------------------------------------------------------------------- /lib/thirdPartyCodes/gitPaths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This codes is modified from "gitPaths.ts" in repository "DonJayamanne/gitHistoryVSCode" 3 | * 4 | * TODO: Use the method likes `getExtension('vscode.git')` to re-implement this module. 5 | * 6 | * @license MIT 7 | * @author DonJayamanne 8 | * @see https://github.com/DonJayamanne/gitHistoryVSCode/blob/master/src/adapter/exec/gitCommandExec.ts 9 | * @see https://github.com/DonJayamanne/gitHistoryVSCode/blob/master/src/adapter/repository/git.ts 10 | */ 11 | 12 | //@ts-check 13 | /// 14 | 15 | let vscode = require('vscode'); 16 | let log = require('../Log'); 17 | let { spawn, exec } = require('child_process'), 18 | fs = require('fs'), 19 | path = require('path'); 20 | 21 | // logger wrapper for original codes 22 | let logger = { logInfo: log.debug, logError: log.error }; 23 | let gitPath = void 0; 24 | 25 | module.exports = { 26 | getGitBranch, 27 | getGitPath, 28 | getGitRepositoryPath 29 | }; 30 | 31 | /** @returns {Promise} */ 32 | function getGitPath() { 33 | if (gitPath !== undefined) { 34 | return Promise.resolve(gitPath); 35 | } 36 | return new Promise((resolve, reject) => { 37 | const gitPathConfig = vscode.workspace.getConfiguration('git').get('path'); 38 | if (typeof gitPathConfig === 'string' && gitPathConfig.length > 0) { 39 | if (fs.existsSync(gitPathConfig)) { 40 | logger.logInfo(`git path: ${gitPathConfig} - from vscode settings`); 41 | gitPath = gitPathConfig; 42 | resolve(gitPathConfig); 43 | return; 44 | } 45 | else { 46 | logger.logError(`git path: ${gitPathConfig} - from vscode settings in invalid`); 47 | } 48 | } 49 | 50 | if (process.platform !== 'win32') { 51 | logger.logInfo(`git path: using PATH environment variable`); 52 | gitPath = 'git'; 53 | resolve('git'); 54 | return; 55 | } 56 | else { 57 | // in Git for Windows, the recommendation is not to put git into the PATH. 58 | // Instead, there is an entry in the Registry. 59 | let regQueryInstallPath = (location, view) => { 60 | return new Promise((resolve, reject) => { 61 | let callback = function (error, stdout, stderr) { 62 | if (error && error.code !== 0) { 63 | error.stdout = stdout.toString(); 64 | error.stderr = stderr.toString(); 65 | reject(error); 66 | return; 67 | } 68 | 69 | let installPath = stdout.toString().match(/InstallPath\s+REG_SZ\s+([^\r\n]+)\s*\r?\n/i)[1]; 70 | if (installPath) { 71 | resolve(installPath + '\\bin\\git'); 72 | } else { 73 | reject(); 74 | } 75 | }; 76 | 77 | let viewArg = ''; 78 | switch (view) { 79 | case '64': viewArg = '/reg:64'; break; 80 | case '32': viewArg = '/reg:64'; break; 81 | default: break; 82 | } 83 | 84 | exec('reg query ' + location + ' ' + viewArg, callback); 85 | }); 86 | }; 87 | 88 | let queryChained = (locations) => { 89 | return new Promise((resolve, reject) => { 90 | if (locations.length === 0) { 91 | reject('None of the known git Registry keys were found'); 92 | return; 93 | } 94 | 95 | let location = locations[0]; 96 | regQueryInstallPath(location.key, location.view).then( 97 | (location) => resolve(location), 98 | (error) => queryChained(locations.slice(1)).then( 99 | (location) => resolve(location), 100 | (error) => reject(error) 101 | ) 102 | ); 103 | }); 104 | }; 105 | 106 | queryChained([ 107 | { key: 'HKCU\\SOFTWARE\\GitForWindows', view: null }, // user keys have precendence over 108 | { key: 'HKLM\\SOFTWARE\\GitForWindows', view: null }, // machine keys 109 | { key: 'HKCU\\SOFTWARE\\GitForWindows', view: '64' }, // default view (null) before 64bit view 110 | { key: 'HKLM\\SOFTWARE\\GitForWindows', view: '64' }, 111 | { key: 'HKCU\\SOFTWARE\\GitForWindows', view: '32' }, // last is 32bit view, which will only be checked 112 | { key: 'HKLM\\SOFTWARE\\GitForWindows', view: '32' }]). // for a 32bit git installation on 64bit Windows 113 | then( 114 | (path) => { 115 | logger.logInfo(`git path: ${path} - from registry`); 116 | gitPath = path; 117 | resolve(path); 118 | }, 119 | (error) => { 120 | logger.logInfo(`git path: falling back to PATH environment variable`); 121 | gitPath = 'git'; 122 | resolve('git'); 123 | }); 124 | } 125 | }); 126 | } 127 | 128 | /** @returns {Promise} */ 129 | function getGitRepositoryPath(fileName = '') { 130 | return new Promise((resolve, reject) => { 131 | getGitPath().then(gitPath => { 132 | const directory = fs.existsSync(fileName) && fs.statSync(fileName).isDirectory() ? fileName : path.dirname(fileName); 133 | const options = { cwd: directory }; 134 | const args = ['rev-parse', '--show-toplevel']; 135 | 136 | // logger.logInfo('git ' + args.join(' ')); 137 | let ls = spawn(gitPath, args, options); 138 | 139 | let repoPath = ''; 140 | let error = ''; 141 | ls.stdout.on('data', function (data) { 142 | repoPath += data + '\n'; 143 | }); 144 | 145 | ls.stderr.on('data', function (data) { 146 | error += data; 147 | }); 148 | 149 | ls.on('error', function (error) { 150 | logger.logError(error); 151 | reject(error); 152 | return; 153 | }); 154 | 155 | ls.on('close', function () { 156 | if (error.length > 0) { 157 | logger.logInfo(error); //logError => logInfo such as "repository is not exist" 158 | // reject(error); 159 | return resolve(null); 160 | } 161 | let repositoryPath = repoPath.trim(); 162 | if (!path.isAbsolute(repositoryPath)) { 163 | repositoryPath = path.join(path.dirname(fileName), repositoryPath); 164 | } 165 | logger.logInfo('git repo path: ' + repositoryPath); 166 | resolve(repositoryPath); 167 | }); 168 | }).catch(reject); 169 | }); 170 | } 171 | 172 | /** @returns {Promise} */ 173 | function getGitBranch(repoPath = '') { 174 | return new Promise((resolve, reject) => { 175 | getGitPath().then(gitPath => { 176 | const options = { cwd: repoPath }; 177 | const args = ['rev-parse', '--abbrev-ref', 'HEAD']; 178 | let branch = ''; 179 | let error = ''; 180 | let ls = spawn(gitPath, args, options); 181 | ls.stdout.on('data', function (data) { 182 | branch += data.slice(0, -1); 183 | }); 184 | 185 | ls.stderr.on('data', function (data) { 186 | error += data; 187 | }); 188 | 189 | ls.on('error', function (error) { 190 | logger.logError(error); 191 | reject(error); 192 | return; 193 | }); 194 | 195 | ls.on('close', function () { 196 | resolve(branch); 197 | }); 198 | }).catch(reject); 199 | }); 200 | } 201 | -------------------------------------------------------------------------------- /lib/LocalServer.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | /* 4 | What does this module work: 5 | 6 | 1. launch a local tracking server 7 | 2. Passing a local tracking server from other VSCode windows context 8 | - (It means stop local tracking server running in other VSCode windows context, and start a new in this windows) 9 | 3. Stop a local tracking server 10 | 4. Open tracking server report page 11 | */ 12 | 13 | const ENABLE_PIPE_SERVER_OUTPUT = false; 14 | 15 | //default open new server listening 10345 port 16 | const DEFAULT_PORT = 10345; 17 | //Just a true const value(for readability) 18 | const SILENT_START_SERVER = true; 19 | 20 | //som parameters for start local server 21 | const EXECUTE_CWD = `${__dirname}/../node_modules/vscode-coding-tracker-server/`; 22 | const EXECUTE_CWD_DEV = `${__dirname}/../../vscode-coding-tracker-server`; 23 | const EXECUTE_SCRIPT = 'app.js'; 24 | const EXECUTE_PARAMETERS = [ 25 | '--local', 26 | '--debug', 27 | '--public-report', 28 | `-o`,`${process.env.USERPROFILE||process.env.HOME}/.coding-tracker/`, 29 | `--port={0}`, 30 | `--token={1}` 31 | ]; 32 | 33 | 34 | const URL = require('url'); 35 | const fs = require('fs'); 36 | const { fork, exec } = require('child_process'); 37 | const vscode = require('vscode'); 38 | const request = require('request'); 39 | 40 | const log = require('./OutputChannelLog'); 41 | const statusBar = require('./StatusBarManager'); 42 | const ext = require('./VSCodeHelper'); 43 | 44 | //User config read from vscode configurations 45 | var userConfig = { 46 | url: '', 47 | token: '', 48 | localMode: false 49 | }; 50 | 51 | //Is local server running under this windows context 52 | var isLocalServerRunningInThisContext = false; 53 | //An child_process spawn object 54 | var serverChildProcess; 55 | 56 | 57 | //init function. 58 | //it will be call when extension active 59 | function init(extensionContext) { 60 | var {subscriptions} = extensionContext; 61 | //Register commands 62 | subscriptions.push(vscode.commands.registerCommand('codingTracker.showReport', showReport)); 63 | subscriptions.push(vscode.commands.registerCommand('codingTracker.startLocalServer', () => startLocalServer())); 64 | subscriptions.push(vscode.commands.registerCommand('codingTracker.stopLocalServer', () => stopLocalServer())); 65 | 66 | //Read user config 67 | userConfig = _readUserConfig(); 68 | 69 | //if turn on local mode 70 | if (userConfig.localMode) { 71 | //Kill/stop old server by requesting /ajax/kill 72 | log.debug(`[LocalMode]: try to kill old tracking server...`); 73 | request.get(_getKillURL(), { qs: { token: userConfig.token } }, (err, res, bd) => { 74 | if (err) { 75 | log.debug(`[LocalMode]: there are no old tracking server, just opening a new local server...`); 76 | return startLocalServer(true);// start a new server 77 | } 78 | var result = {}; 79 | try { result = JSON.parse(bd); } catch (e) { log.error('[Error]: parse JSON failed!'); } 80 | if (result.success) { 81 | log.debug('[Killed]: killed old server! and opening a new local server...'); 82 | return startLocalServer(true); //start a new server 83 | } else { 84 | log.debug(`[Response]: ${bd}`); 85 | (result.error || '').indexOf('local') >= 0 ? 86 | _showError(`Starting the local mode failed!(because the existed server is not a local server)`, { stack: 'Not a local server!' }) : 87 | _showError(`Starting the local mode failed!(because your token is invalid for existed server)`, { stack: 'token is invalid' }); 88 | } 89 | }); 90 | } 91 | } 92 | 93 | //This function will be call when vscode configurations changed 94 | function updateConfig() { 95 | var newConfig = _readUserConfig(); 96 | //If the local server is running in this windows and related configurations changed 97 | // then show a information tip "please reload VSCode" 98 | if (isLocalServerRunningInThisContext) { 99 | var shouldToastReloadVSCode = false; 100 | if (userConfig.localMode) { 101 | if (userConfig.token != newConfig.token || userConfig.url != newConfig.url || newConfig.localMode == false) 102 | shouldToastReloadVSCode = true; 103 | } else if (newConfig.localMode == true) { 104 | shouldToastReloadVSCode = true; 105 | } 106 | log.debug(`[ConfigChanged]: please reload vscode to apply it.`); 107 | shouldToastReloadVSCode && vscode.window.showInformationMessage( 108 | `CodingTracker: detected your local server configurations changed. Please reload VSCode to apply.`); 109 | } 110 | //save new configurations 111 | userConfig = newConfig; 112 | } 113 | 114 | //Start a new local server in this windows context 115 | //silent == true: there will no any success message box display to user(but user still could see exception info if start failed) 116 | function startLocalServer(silent) { 117 | let cwd = EXECUTE_CWD; 118 | if (fs.existsSync(EXECUTE_CWD_DEV)) 119 | cwd = EXECUTE_CWD_DEV; 120 | 121 | let s = fork(EXECUTE_SCRIPT, _getLaunchParameters(), { cwd, silent: true }); 122 | s.stdout.setEncoding('utf8'); 123 | s.stderr.setEncoding('utf8'); 124 | s.stdout.on('data', onServerStdout); 125 | s.stderr.on('data', onServerStderr); 126 | s.on('error', err => { 127 | isLocalServerRunningInThisContext = false; 128 | serverChildProcess = null; 129 | _showError(`start local server failed!`, err); 130 | }); 131 | // s.on('exit', code => '"exit" event could not indicated child_process fully exit'+ 132 | // 'child process maybe create a new child_process, just like `npm start`'); 133 | s.on('close', (code, signal) => { 134 | isLocalServerRunningInThisContext = false; 135 | serverChildProcess = null; 136 | code && _showError(`local server exit with code ${code}!(Have you launched another local server?)`, { 137 | stack: `[Exit] exit code: ${code}` 138 | }); 139 | }); 140 | serverChildProcess = s; 141 | isLocalServerRunningInThisContext = true; 142 | log.debug(`[Launch]: Local server launching...`); 143 | _checkIsLocalServerStart(silent, 0, false); 144 | 145 | 146 | function onServerStdout(data) { 147 | data = String(data); 148 | if (!ENABLE_PIPE_SERVER_OUTPUT && data.indexOf('Server started!') < 0) 149 | return; 150 | data.split('\n').forEach(it => log.debug(`[LocalServer/stdout]: ${it}`)); 151 | } 152 | function onServerStderr(data) { 153 | String(data).split('\n').forEach(it => log.error(`[LocalServer/stderr]: ${it}`)); 154 | } 155 | } 156 | 157 | /** 158 | * Check is the local server started by requesting welcome information page 159 | * 160 | * @param {boolean} silent : if local server started, there are no any message 161 | * @param {number} times : how many times retry 162 | * @param {boolean} isActiveCheck : is this calling from active heart beat (It will be change log content) 163 | * @returns 164 | */ 165 | function _checkIsLocalServerStart(silent, times, isActiveCheck) { 166 | if (times >= 10) return statusBar.localServer.turnOff(); 167 | _checkConnection((networkErr, serverErr, result) => { 168 | if (result) { 169 | if (result.localServerMode) { 170 | silent || vscode.window.showInformationMessage(`CodingTracker: local server started!`); 171 | isActiveCheck ? log.debug(`[Heartbeat]: server in local!`) : 172 | log.debug(`[Launched]: Local server has launching!`); 173 | statusBar.localServer.turnOn(); 174 | } else { 175 | statusBar.localServer.turnOff(); 176 | } 177 | } else if (!networkErr) { 178 | //connect success, but not local server 179 | return; 180 | } else { 181 | setTimeout(() => _checkIsLocalServerStart(silent, times + 1, isActiveCheck), 800); 182 | } 183 | }) 184 | } 185 | 186 | //Stop local server by requesting API /ajax/kill 187 | function stopLocalServer() { 188 | log.debug(`[Kill]: try to kill local server...`); 189 | request.get(_getKillURL(), { qs: { token: userConfig.token } }, (err, res, bd) => { 190 | if (err) return _showError(`kill failed, because could not connect local server`, err); 191 | var result = {}; 192 | try { result = JSON.parse(bd); } catch (e) { log.error('[Error]: parse JSON failed!'); } 193 | if (result.success) { 194 | statusBar.localServer.turnOff() 195 | log.debug(`[Killed]: killed local server!`); 196 | vscode.window.showInformationMessage(`CodingTracker: local server stopped!`); 197 | } else { 198 | log.debug(`[Response]: ${bd}`); 199 | (result.error || '').indexOf('local') >= 0 ? 200 | _showError(`stop failed!(because this server is not a local server)`, { stack: 'Not a local server!' }) : 201 | _showError(`stop failed!(because your token is invalid)`, { stack: 'token is invalid' }); 202 | } 203 | }); 204 | } 205 | 206 | //Stop local server running in this window by child process tree killing 207 | function stopLocalServerSilentByTreeKill() { 208 | log.debug('[Kill]: try to kill local server by tree kill way...'); 209 | if (serverChildProcess && serverChildProcess.pid) 210 | require('tree-kill')(serverChildProcess.pid); 211 | serverChildProcess = null; 212 | } 213 | 214 | //Open your coding time report page 215 | function showReport() { 216 | exec(_getOpenReportCommand(), err => 217 | err && _showError(`Execute open report command error!`, err)); 218 | } 219 | 220 | //check is the server connectable 221 | //then(networkError, serverError, responseBody) 222 | function _checkConnection(then) { 223 | request.get(_getWelcomeURL(), {}, (err, res, bd) => { 224 | if (err) return then(err); 225 | if (res.statusCode != 200) return then(null, `server exception!(${res.statusCode})`); 226 | try { var result = JSON.parse(bd); } catch (e) { return then(null, `server exception!(illegal welcome json)`); } 227 | return then(null, null, result); 228 | }); 229 | } 230 | 231 | //Generate command for open report page in different OS 232 | function _getOpenReportCommand() { 233 | const openURLIn = { 234 | win32: url => `start "" "${url}"`, 235 | darwin: url => `open "${url}"`, 236 | }; 237 | return (openURLIn[process.platform] || (url => `xdg-open "${url}"`))(_getReportURL()); 238 | } 239 | function _getReportURL() { return `${userConfig.url}/report/` + (userConfig.token ? `?token=${userConfig.token}` : ``) } 240 | function _getTestURL() { return `${userConfig.url}/ajax/test` } 241 | function _getWelcomeURL() { return `${userConfig.url}/` } 242 | function _getKillURL() { return `${userConfig.url}/ajax/kill` } 243 | function _readUserConfig() { 244 | var configurations = ext.getConfig('codingTracker'), 245 | token = String(configurations.get('uploadToken')), 246 | url = String(configurations.get('serverURL')), 247 | localMode = Boolean(configurations.get('localServerMode')); 248 | url = url.endsWith('/') ? url.slice(0, -1) : url; 249 | return { url, token, localMode }; 250 | } 251 | 252 | function _showError(errOneLine, errObject) { 253 | const MENU_ITEM_TEXT = 'Show details' 254 | log.error(`[Error]: ${errOneLine}\n${errObject.stack}`); 255 | vscode.window.showErrorMessage(`CodingTracker: ${errOneLine}`, MENU_ITEM_TEXT).then(item => 256 | item == MENU_ITEM_TEXT ? log.show() : 0); 257 | } 258 | function _getLaunchParameters() { 259 | var ps = []; 260 | for (let i = 0; i < EXECUTE_PARAMETERS.length; i++) 261 | ps.push(EXECUTE_PARAMETERS[i] 262 | .replace('{0}', _getPortInfoFromURL(userConfig.url)) 263 | .replace('{1}', userConfig.token)); 264 | return ps; 265 | } 266 | function _getPortInfoFromURL(url) { return String(URL.parse(url).port || DEFAULT_PORT); } 267 | 268 | module.exports = { 269 | init, 270 | updateConfig, 271 | activeCheckIsLocalServerStart: () => _checkIsLocalServerStart(true, 9, true), 272 | detectOldSever_SoStartANewIfUnderLocalMode: () => { 273 | if (!userConfig.localMode) 274 | return false; 275 | if (!isLocalServerRunningInThisContext) { 276 | log.debug('[Launch]: launching a new tracking server because detected old server exited!'); 277 | startLocalServer(SILENT_START_SERVER); 278 | return true; 279 | } 280 | return false; 281 | }, 282 | dispose: stopLocalServerSilentByTreeKill 283 | }; 284 | -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | /// 3 | 4 | const vscode = require('vscode'); 5 | 6 | const ext = require('./lib/VSCodeHelper'); 7 | const uploader = require('./lib/Uploader'); 8 | const log = require('./lib/Log'); 9 | const statusBar = require('./lib/StatusBarManager'); 10 | const localServer = require('./lib/LocalServer'); 11 | const uploadObject = require('./lib/UploadObject'); 12 | 13 | const { isDebugMode } = require('./lib/Constants'); 14 | const { getProxyConfiguration } = require('./lib/GetProxyConfiguration'); 15 | const { generateDiagnoseLogFile } = require('./lib/EnvironmentProbe'); 16 | 17 | /** How many ms in 1s */ 18 | const SECOND = 1000; 19 | 20 | /** shortest time to record coding. 5000 means: All coding record divide by 5000 */ 21 | const CODING_SHORTEST_UNIT_MS = 5 * SECOND; 22 | 23 | /** at least time to upload a watching(open) record */ 24 | const AT_LEAST_WATCHING_TIME = 5 * SECOND; 25 | 26 | /** 27 | * means if you are not intently watching time is more than this number 28 | * the watching track will not be continuously but a new record 29 | */ 30 | const MAX_ALLOW_NOT_INTENTLY_MS = 60 * SECOND; 31 | 32 | /** if you have time below not coding(pressing your keyboard), the coding track record will be upload and re-track */ 33 | const MAX_CODING_WAIT_TIME = 30 * SECOND; 34 | 35 | /** If there a event onFileCoding with scheme in here, just ignore this event */ 36 | const INVALID_CODING_DOCUMENT_SCHEMES = [ 37 | //there are will be a `onDidChangeTextDocument` with document scheme `git-index` 38 | //be emitted when you switch document, so ignore it 39 | 'git-index', 40 | //since 1.9.0 vscode changed `git-index` to `git`, OK, they are refactoring around source control 41 | //see more: https://code.visualstudio.com/updates/v1_9#_contributable-scm-providers 42 | 'git', 43 | //when you just look up output channel content, there will be a `onDidChangeTextDocument` be emitted 44 | 'output', 45 | //This is a edit event emit from you debug console input box 46 | 'input', 47 | //This scheme is appeared in vscode global replace diff preview editor 48 | 'private', 49 | //This scheme is used for markdown preview document 50 | //It will appear when you edit a markdown with aside preview 51 | 'markdown' 52 | ]; 53 | 54 | const EMPTY = { document: null, textEditor: null }; 55 | 56 | /** more thinking time from user configuration */ 57 | let moreThinkingTime = 0; 58 | /** current active document*/ 59 | let activeDocument; 60 | /** Tracking data, record document open time, first coding time and last coding time and coding time long */ 61 | let trackData = { 62 | openTime: 0, 63 | lastIntentlyTime: 0, 64 | firstCodingTime: 0, 65 | codingLong: 0, 66 | lastCodingTime: 0 67 | }; 68 | let resetTrackOpenAndIntentlyTime = (now) => { trackData.openTime = trackData.lastIntentlyTime = now }; 69 | 70 | /** 71 | * Uploading open track data 72 | * @param {number} now 73 | */ 74 | function uploadOpenTrackData(now) { 75 | //If active document is not a ignore document 76 | if (!isIgnoreDocument(activeDocument)) { 77 | let longest = trackData.lastIntentlyTime + MAX_ALLOW_NOT_INTENTLY_MS + moreThinkingTime, 78 | long = Math.min(now, longest) - trackData.openTime; 79 | 80 | uploadObject.generateOpen(activeDocument, trackData.openTime, long) 81 | .then(uploader.upload); 82 | } 83 | resetTrackOpenAndIntentlyTime(now); 84 | } 85 | 86 | /** Uploading coding track data and retracking coding track data */ 87 | function uploadCodingTrackData() { 88 | //If active document is not a ignore document 89 | if (!isIgnoreDocument(activeDocument)) { 90 | uploadObject.generateCode(activeDocument, trackData.firstCodingTime, trackData.codingLong) 91 | .then(uploader.upload); 92 | } 93 | //Re-tracking coding track data 94 | trackData.codingLong = 95 | trackData.lastCodingTime = 96 | trackData.firstCodingTime = 0; 97 | } 98 | 99 | /** Check a TextDocument, Is it a ignore document(null/'inmemory') */ 100 | function isIgnoreDocument(doc) { 101 | return !doc || doc.uri.scheme == 'inmemory'; 102 | } 103 | 104 | /** Handler VSCode Event */ 105 | let EventHandler = { 106 | /** @param {vscode.TextEditor} doc */ 107 | onIntentlyWatchingCodes: (textEditor) => { 108 | // if (isDebugMode) 109 | // log.debug('watching intently: ' + ext.dumpEditor(textEditor)); 110 | if (!textEditor || !textEditor.document) 111 | return;//Empty document 112 | let now = Date.now(); 113 | //Long time have not intently watching document 114 | if (now > trackData.lastIntentlyTime + MAX_ALLOW_NOT_INTENTLY_MS + moreThinkingTime) { 115 | uploadOpenTrackData(now); 116 | //uploadOpenTrackDate has same expression as below: 117 | //resetTrackOpenAndIntentlyTime(now); 118 | } else { 119 | trackData.lastIntentlyTime = now; 120 | } 121 | }, 122 | /** @param {vscode.TextDocument} doc */ 123 | onActiveFileChange: (doc) => { 124 | // if(isDebugMode) 125 | // log.debug('active file change: ' + ext.dumpDocument(doc)); 126 | let now = Date.now(); 127 | // If there is a TextEditor opened before changed, should upload the track data 128 | if (activeDocument) { 129 | //At least open 5 seconds 130 | if (trackData.openTime < now - AT_LEAST_WATCHING_TIME) { 131 | uploadOpenTrackData(now); 132 | } 133 | //At least coding 1 second 134 | if (trackData.codingLong) { 135 | uploadCodingTrackData(); 136 | } 137 | } 138 | activeDocument = ext.cloneTextDocument(doc); 139 | //Retracking file open time again (Prevent has not retracked open time when upload open tracking data has been called) 140 | resetTrackOpenAndIntentlyTime(now); 141 | trackData.codingLong = trackData.lastCodingTime = trackData.firstCodingTime = 0; 142 | }, 143 | /** @param {vscode.TextDocument} doc */ 144 | onFileCoding: (doc) => { 145 | 146 | // onFileCoding is an alias of event `onDidChangeTextDocument` 147 | // 148 | // Here is description of this event excerpt from vscode extension docs page. 149 | // (Link: https://code.visualstudio.com/docs/extensionAPI/vscode-api) 150 | // ``` 151 | // An event that is emitted when a text document is changed. 152 | // This usually happens when the contents changes but also when other things like the dirty - state changes. 153 | // ``` 154 | 155 | // if(isDebugMode) 156 | // log.debug('coding: ' + ext.dumpDocument(doc)); 157 | 158 | // vscode bug: 159 | // Event `onDidChangeActiveTextEditor` be emitted with empty document when you open "Settings" editor. 160 | // Then Event `onDidChangeTextDocument` be emitted even if you has not edited anything in setting document. 161 | // I ignore empty activeDocument to keeping tracker up and avoiding exception like follow: 162 | // TypeError: Cannot set property 'lineCount' of null // activeDocument.lineCount = ... 163 | if (!activeDocument) 164 | return ; 165 | 166 | // Ignore the invalid coding file schemes 167 | if (!doc || INVALID_CODING_DOCUMENT_SCHEMES.indexOf(doc.uri.scheme) >= 0 ) 168 | return; 169 | 170 | if (isDebugMode) { 171 | // fragment in this if condition is for catching unknown document scheme 172 | let { uri } = doc, { scheme } = uri; 173 | if (scheme != 'file' && 174 | scheme != 'untitled' && 175 | scheme != 'debug' && 176 | //scheme in vscode user settings (or quick search bar in user settings) 177 | // vscode://defaultsettings/{0...N}/settings.json 178 | scheme != 'vscode' && 179 | //scheme in vscode ineractive playground 180 | scheme != 'walkThroughSnippet') { 181 | // vscode.window.showInformationMessage(`Unknown uri scheme(details in console): ${scheme}: ${uri.toString()}`); 182 | log.debug(ext.dumpDocument(doc)); 183 | } 184 | } 185 | 186 | let now = Date.now(); 187 | //If time is too short to calling this function then just ignore it 188 | if (now - CODING_SHORTEST_UNIT_MS < trackData.lastCodingTime) 189 | return; 190 | // Update document line count 191 | activeDocument.lineCount = doc.lineCount; 192 | 193 | //If is first time coding in this file, record time 194 | if (!trackData.firstCodingTime) 195 | trackData.firstCodingTime = now; 196 | //If too long time to recoding, so upload old coding track and retracking 197 | else if (trackData.lastCodingTime < now - MAX_CODING_WAIT_TIME - moreThinkingTime) {//30s 198 | uploadCodingTrackData() 199 | //Reset first coding time 200 | trackData.firstCodingTime = now; 201 | } 202 | trackData.codingLong += CODING_SHORTEST_UNIT_MS; 203 | trackData.lastCodingTime = now; 204 | } 205 | } 206 | 207 | /** when extension launch or vscode config change */ 208 | function updateConfigurations() { 209 | //CodingTracker Configuration 210 | const extensionCfg = ext.getConfig('codingTracker'); 211 | const uploadToken = String(extensionCfg.get('uploadToken')); 212 | const computerId = String(extensionCfg.get('computerId')); 213 | const enableStatusBar = extensionCfg.get('showStatus'); 214 | let mtt = parseInt(extensionCfg.get('moreThinkingTime')); 215 | let uploadURL = String(extensionCfg.get('serverURL')); 216 | 217 | const httpCfg = ext.getConfig('http'); 218 | const baseHttpProxy = httpCfg ? httpCfg.get('proxy') : undefined; 219 | 220 | const overwriteHttpProxy = extensionCfg.get('proxy'); 221 | const proxy = getProxyConfiguration(baseHttpProxy, overwriteHttpProxy); 222 | 223 | // fixed wrong more thinking time configuration value 224 | if (isNaN(mtt)) mtt = 0; 225 | if (mtt < -15 * SECOND) mtt = -15 * SECOND; 226 | moreThinkingTime = mtt; 227 | 228 | uploadURL = (uploadURL.endsWith('/') ? uploadURL : (uploadURL + '/')) + 'ajax/upload'; 229 | uploader.set(uploadURL, uploadToken, proxy); 230 | uploadObject.init(computerId || `unknown-${require('os').platform()}`); 231 | 232 | localServer.updateConfig(); 233 | statusBar.init(enableStatusBar); 234 | } 235 | 236 | function activate(context) { 237 | generateDiagnoseLogFile(); 238 | 239 | //Declare for add disposable inside easy 240 | let subscriptions = context.subscriptions; 241 | 242 | uploadObject.init(); 243 | 244 | //Initialize local server(launch local server if localServer config is true) 245 | localServer.init(context); 246 | 247 | //Initialize Uploader Module 248 | uploader.init(); 249 | //Update configurations first time 250 | updateConfigurations(); 251 | 252 | //Listening workspace configurations change 253 | vscode.workspace.onDidChangeConfiguration(updateConfigurations); 254 | 255 | //Tracking the file display when vscode open 256 | EventHandler.onActiveFileChange( (vscode.window.activeTextEditor || EMPTY).document); 257 | 258 | //Listening vscode event to record coding activity 259 | subscriptions.push(vscode.workspace.onDidChangeTextDocument(e => EventHandler.onFileCoding( (e || EMPTY).document) )); 260 | subscriptions.push(vscode.window.onDidChangeActiveTextEditor(e => EventHandler.onActiveFileChange((e || EMPTY).document ) )); 261 | //the below event happen means you change the cursor in the document. 262 | //So It means you are watching so intently in some ways 263 | subscriptions.push(vscode.window.onDidChangeTextEditorSelection(e => EventHandler.onIntentlyWatchingCodes((e || EMPTY).textEditor) )); 264 | 265 | // Maybe I will add "onDidChangeVisibleTextEditors" in extension in next version 266 | // For detect more detailed editor information 267 | // But this event will always include debug-input box if you open debug panel one time 268 | 269 | // subscriptions.push(vscode.window.onDidChangeVisibleTextEditors(e => log.debug('onDidChangeVisibleTextEditors', e))) 270 | 271 | // debug command 272 | // subscriptions.push(vscode.commands.registerCommand('codingTracker.dumpVCSQueue', () => { 273 | // log.debug(require('./lib/vcs/Git')._getVCSInfoQueue); 274 | // })); 275 | } 276 | function deactivate() { 277 | EventHandler.onActiveFileChange(null); 278 | localServer.dispose(); 279 | log.end(); 280 | } 281 | 282 | 283 | exports.activate = activate; 284 | exports.deactivate = deactivate; 285 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------