├── .buildconfig ├── .gitignore ├── LICENSE ├── README.md ├── README.zh.md ├── SNAPSHOT ├── ddl.png ├── home.png └── main.png ├── data ├── cn.navclub.ldbfx.appdata.xml.in ├── cn.navclub.ldbfx.desktop.in ├── cn.navclub.ldbfx.gschema.xml ├── icons │ ├── hicolor │ │ ├── scalable │ │ │ ├── actions │ │ │ │ ├── dbfx-add-folder.svg │ │ │ │ ├── dbfx-alert-error.svg │ │ │ │ ├── dbfx-alert-help.svg │ │ │ │ ├── dbfx-alert-info.svg │ │ │ │ ├── dbfx-alert-warn.svg │ │ │ │ ├── dbfx-break-off.svg │ │ │ │ ├── dbfx-close.svg │ │ │ │ ├── dbfx-connect.svg │ │ │ │ ├── dbfx-copy.svg │ │ │ │ ├── dbfx-ddl.svg │ │ │ │ ├── dbfx-devel.svg │ │ │ │ ├── dbfx-folder.svg │ │ │ │ ├── dbfx-fpage.svg │ │ │ │ ├── dbfx-lpage.svg │ │ │ │ ├── dbfx-npage.svg │ │ │ │ ├── dbfx-ppage.svg │ │ │ │ ├── dbfx-prop.svg │ │ │ │ ├── dbfx-refresh.svg │ │ │ │ ├── dbfx-sql.svg │ │ │ │ ├── dbfx-stop.svg │ │ │ │ ├── dbfx-table.svg │ │ │ │ ├── dbfx-view.svg │ │ │ │ └── v-dbfx.svg │ │ │ ├── apps │ │ │ │ └── cn.navclub.ldbfx.svg │ │ │ └── categories │ │ │ │ ├── dbfx-mysql.svg │ │ │ │ ├── dbfx-schema.svg │ │ │ │ └── dbfx-sqlite.svg │ │ └── symbolic │ │ │ └── apps │ │ │ └── cn.navclub.ldbfx-symbolic.svg │ └── meson.build ├── ldbfx_vala.gresource.xml ├── meson.build ├── mono_kai_extended.xml ├── style │ └── style.css └── ui │ ├── connect-dialog.xml │ ├── data-source-compact.xml │ ├── design-table.xml │ ├── fx-alert.xml │ ├── main-window.xml │ ├── menubar.xml │ ├── notebook-ddl.xml │ ├── notebook-table.xml │ └── sqlite-compact.xml ├── flatpak └── cn.navclub.ldbfx.json ├── lib ├── async │ └── async-work.vala ├── config │ ├── constant.vala │ ├── datasource.vala │ ├── db-feature.vala │ ├── error-domain.vala │ └── table-column-type.vala ├── connection-pool.vala ├── connection.vala ├── impl │ ├── mysql-connection.vala │ └── sqlite-connection.vala ├── meson.build ├── model │ ├── database-info.vala │ ├── database-schema.vala │ ├── page-query.vala │ ├── table-column.vala │ └── table-info.vala └── util │ └── str-util.vala ├── meson.build ├── meson_post_install.py ├── po ├── LINGUAS ├── POTFILES ├── en_US.po ├── meson.build └── zh_CN.po ├── src ├── config │ ├── app-config.vala │ └── nav-tree.vala ├── controller │ └── main-controller.vala ├── dialog │ ├── connect-dialog.vala │ └── design-table.vala ├── event │ └── nav-tree-event.vala ├── main.vala ├── meson.build ├── model │ └── table-row-meta.vala ├── tab-service.vala ├── util │ ├── json-util.vala │ ├── os-util.vala │ ├── sql-util.vala │ └── ui-util.vala └── widget │ ├── data-source-compact.vala │ ├── fx-alert.vala │ ├── notebook-ddl.vala │ ├── notebook-tab.vala │ ├── notebook-table.vala │ ├── sql-source-view.vala │ └── sqlite-compact.vala └── vapi ├── config.vapi └── libmariadb.vapi /.buildconfig: -------------------------------------------------------------------------------- 1 | [default] 2 | name=Default 3 | runtime=host 4 | toolchain=default 5 | config-opts= 6 | run-opts= 7 | prefix=/home/yangkui/.cache/gnome-builder/install/v-dbfx/host 8 | app-id= 9 | postbuild= 10 | prebuild= 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | .idea 3 | .vscode 4 | cmake-build 5 | cmake-build-debug -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ldbfx 2 | 3 | #### Description 4 | **ldbfx** A visual database management software for Linux 5 | 6 | Download on Flathub 7 | 8 | 9 | ### Snapshot 10 | 11 | + Program home 12 | 13 | ![Load fail](SNAPSHOT/home.png) 14 | 15 | + Program data view 16 | 17 | ![Load fail](SNAPSHOT/main.png) 18 | 19 | + Sql statement editor 20 | ![Load fail](SNAPSHOT/ddl.png) 21 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | # ldbfx 2 | 3 | #### 概述 4 | 5 | **ldbfx** 一款基于linux平台可视化数据库管理软件 6 | 7 | Download on Flathub 8 | 9 | 10 | ### 快照 11 | 12 | + 主界面 13 | 14 | ![Load fail](SNAPSHOT/home.png) 15 | 16 | + 数据展示页 17 | 18 | ![Load fail](SNAPSHOT/main.png) 19 | 20 | + Sql语句编辑器 21 | ![Load fail](SNAPSHOT/ddl.png) 22 | -------------------------------------------------------------------------------- /SNAPSHOT/ddl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/databasefx/ldbfx/24b74a58b4457ae3c6a4d038c11e96bdbfb20e6f/SNAPSHOT/ddl.png -------------------------------------------------------------------------------- /SNAPSHOT/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/databasefx/ldbfx/24b74a58b4457ae3c6a4d038c11e96bdbfb20e6f/SNAPSHOT/home.png -------------------------------------------------------------------------------- /SNAPSHOT/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/databasefx/ldbfx/24b74a58b4457ae3c6a4d038c11e96bdbfb20e6f/SNAPSHOT/main.png -------------------------------------------------------------------------------- /data/cn.navclub.ldbfx.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | cn.navclub.ldbfx 4 | 5 | ldbfx 6 | GZYangKui 7 | Visual database management tool 8 | 9 | CC0-1.0 10 | Apache-2.0 11 | 12 | 13 | pointing 14 | keyboard 15 | touch 16 | 17 | 18 | 19 |

20 | ldbfx is a set of database management tools that can create multiple connections to facilitate the management of different types of databases such as mysql, Oracle, PostgreSQL, SQLite, SQL server, MariaDB and mongodb. 21 |

22 |
23 | 24 | cn.navclub.ldbfx.desktop 25 | 26 | 27 | https://github.com/databasefx/ldbfx 28 | https://github.com/databasefx/ldbfx/issues 29 | 30 | 31 | 32 | https://github.com/databasefx/ldbfx/blob/2b436d80859fc1f84a0237518874dc8658ac2b84/SNAPSHOT/home.png?raw=true 33 | 34 | 35 | https://github.com/databasefx/ldbfx/blob/2b436d80859fc1f84a0237518874dc8658ac2b84/SNAPSHOT/main.png?raw=true 36 | 37 | 38 | https://github.com/databasefx/ldbfx/blob/ff54d1517331b69a8e79f0d59d712cf9598299d1/SNAPSHOT/ddl.png?raw=true 39 | 40 | 41 | 42 | 43 | 44 | 45 |
-------------------------------------------------------------------------------- /data/cn.navclub.ldbfx.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=ldbfx 3 | Exec=ldbfx 4 | Icon=cn.navclub.ldbfx 5 | Terminal=false 6 | Type=Application 7 | Categories=GTK; 8 | StartupNotify=true 9 | X-GNOME-UsesNotifications=true 10 | -------------------------------------------------------------------------------- /data/cn.navclub.ldbfx.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-add-folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-alert-error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-alert-help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-alert-info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-alert-warn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-break-off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-connect.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-ddl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-fpage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-lpage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-npage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-ppage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-prop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-sql.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/dbfx-view.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/actions/v-dbfx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/apps/cn.navclub.ldbfx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/categories/dbfx-mysql.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/categories/dbfx-schema.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/scalable/categories/dbfx-sqlite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/hicolor/symbolic/apps/cn.navclub.ldbfx-symbolic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | install_subdir('hicolor',install_dir:join_paths(get_option('datadir'),'icons')) 2 | -------------------------------------------------------------------------------- /data/ldbfx_vala.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ui/main-window.xml 5 | ui/connect-dialog.xml 6 | ui/notebook-table.xml 7 | ui/menubar.xml 8 | ui/fx-alert.xml 9 | ui/sqlite-compact.xml 10 | ui/notebook-ddl.xml 11 | ui/design-table.xml 12 | ui/data-source-compact.xml 13 | style/style.css 14 | 15 | 16 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | gnome = import('gnome') 2 | resource_files = files('ldbfx_vala.gresource.xml') 3 | resources = gnome.compile_resources('cn.navclub.ldbfx',resource_files,c_name:'resources') 4 | 5 | desktop_file = i18n.merge_file( 6 | input: 'cn.navclub.ldbfx.desktop.in', 7 | output: 'cn.navclub.ldbfx.desktop', 8 | type: 'desktop', 9 | po_dir: '../po', 10 | install: true, 11 | install_dir: join_paths(get_option('datadir'), 'applications') 12 | ) 13 | 14 | 15 | desktop_utils = find_program('desktop-file-validate', required: false) 16 | if desktop_utils.found() 17 | test('Validate desktop file', desktop_utils, 18 | args: [desktop_file] 19 | ) 20 | endif 21 | 22 | appstream_file = i18n.merge_file( 23 | input: 'cn.navclub.ldbfx.appdata.xml.in', 24 | output: 'cn.navclub.ldbfx.appdata.xml', 25 | po_dir: '../po', 26 | install: true, 27 | install_dir: join_paths(get_option('datadir'), 'appdata') 28 | ) 29 | 30 | appstream_util = find_program('appstream-util', required: false) 31 | if appstream_util.found() 32 | test('Validate appstream file', appstream_util, 33 | args: ['validate', appstream_file] 34 | ) 35 | endif 36 | 37 | install_data('cn.navclub.ldbfx.gschema.xml', 38 | install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') 39 | ) 40 | 41 | install_data('mono_kai_extended.xml', 42 | install_dir:join_paths(get_option('datadir'),'gtksourceview-5/styles') 43 | ) 44 | 45 | compile_schemas = find_program('glib-compile-schemas', required: false) 46 | if compile_schemas.found() 47 | test('Validate schema file', compile_schemas, 48 | args: ['--strict', '--dry-run', meson.current_source_dir()] 49 | ) 50 | endif 51 | 52 | subdir('icons') 53 | -------------------------------------------------------------------------------- /data/mono_kai_extended.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | Leo Iannacone 26 | <_description>Based on SublimeText Monokai Extended - Generated with tm2gtksw2 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 1 36 | page1 37 | 38 | 39 | vertical 40 | 41 | 42 | vertical 43 | 44 | 45 | 5 46 | 5 47 | 1 48 | 49 | 50 | _Name 51 | 52 | 0 53 | 0 54 | 1 55 | 56 | 57 | 58 | 59 | 60 | True 61 | 62 | 0 63 | 1 64 | 6 65 | 66 | 67 | 68 | 69 | 70 | _Comment 71 | 72 | 1 73 | 0 74 | 1 75 | 76 | 77 | 78 | 79 | 80 | 1 81 | 82 | 1 83 | 1 84 | 6 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 10 98 | 99 | 100 | _Test Connection 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | _Back 128 | 100 129 | 0 130 | 131 | 134 | 135 | 136 | 137 | 138 | _Next 139 | 100 140 | 141 | 144 | 145 | 146 | 147 | 148 | 151 | 152 | 100 153 | Cancel 154 | 155 | 156 | 157 | 158 | cancel 159 | apply 160 | stepBtn 161 | 162 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /data/ui/data-source-compact.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 163 | 164 | -------------------------------------------------------------------------------- /data/ui/design-table.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57 | -------------------------------------------------------------------------------- /data/ui/fx-alert.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 74 | -------------------------------------------------------------------------------- /data/ui/menubar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _File 6 | dbfx-mysql 7 |
8 | 9 | _New Connection 10 | app.new 11 | 12 |
13 |
14 | 15 | _Exit 16 | app.exit 17 | 18 |
19 |
20 | 21 | _View 22 |
23 | 24 | _Full Screen 25 | app.fullscreen 26 | 27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /data/ui/notebook-ddl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | -------------------------------------------------------------------------------- /data/ui/notebook-table.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 118 | 119 | -------------------------------------------------------------------------------- /data/ui/sqlite-compact.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 54 | 55 | -------------------------------------------------------------------------------- /flatpak/cn.navclub.ldbfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id": "cn.navclub.ldbfx", 3 | "runtime": "org.gnome.Sdk", 4 | "runtime-version": "42", 5 | "sdk": "org.gnome.Sdk", 6 | "command": "ldbfx", 7 | "finish-args": [ 8 | "--share=network", 9 | "--share=ipc", 10 | "--socket=fallback-x11", 11 | "--device=dri", 12 | "--socket=wayland", 13 | "--filesystem=home" 14 | ], 15 | "modules": [ 16 | { 17 | "name": "mariadb", 18 | "buildsystem": "cmake-ninja", 19 | "post-install": [ 20 | "cp /app/lib/mariadb/libmariadb.so.3 /app/lib/libmariadb.so.3" 21 | ], 22 | "cleanup": [ 23 | "/bin/", 24 | "/include/", 25 | "/lib/pkgconfig/", 26 | "/lib/mariadb/", 27 | "/man/" 28 | ], 29 | "sources": [ 30 | { 31 | "type": "archive", 32 | "url": "https://github.com/mariadb-corporation/mariadb-connector-c/archive/refs/tags/v3.2.6.zip", 33 | "sha256": "9fe6a161d1c1ff4f74bb87e4c587bbd2a14146a2bc3ba7b677633bfa0445b8b5" 34 | } 35 | ] 36 | }, 37 | { 38 | "name": "gtksourceview", 39 | "buildsystem": "meson", 40 | "sources": [ 41 | { 42 | "type": "archive", 43 | "url": "https://gitlab.gnome.org/GNOME/gtksourceview/-/archive/5.4.1/gtksourceview-5.4.1.zip", 44 | "sha256": "2af967def84fffa56761f11a583c1d080a8c54ec9f17a268ad78d4b91069cbe7" 45 | } 46 | ] 47 | }, 48 | { 49 | "name": "libpg_query", 50 | "buildsystem": "simple", 51 | "build-commands": [ 52 | "make", 53 | "make install DESTDIR=/app" 54 | ], 55 | "sources": [ 56 | { 57 | "type": "git", 58 | "branch": "13-latest", 59 | "url": "https://github.com/GZYangKui/libpg_query.git" 60 | } 61 | ] 62 | }, 63 | { 64 | "name": "ldbfx", 65 | "buildsystem": "meson", 66 | "sources": [ 67 | { 68 | "type": "git", 69 | "url": "https://github.com/databasefx/ldbfx.git" 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /lib/async/async-work.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 任务详情 4 | * 5 | */ 6 | public delegate void WorkDetail(); 7 | 8 | /** 9 | * 10 | * 一个异步任务的封装 11 | * 12 | */ 13 | public class AsyncWork 14 | { 15 | //当前任务优先级 16 | private int priority; 17 | 18 | //任务详情 19 | public WorkDetail work; 20 | 21 | //任务开始时间 22 | public int64 startTime{ 23 | private set; 24 | get; 25 | default = 0; 26 | } 27 | 28 | //任务结束时间 29 | private int64 endTime{ 30 | private set; 31 | get; 32 | default = 0; 33 | } 34 | 35 | //任务耗时 36 | private int64 duration{ 37 | private set{ 38 | duration = value; 39 | } 40 | 41 | get { 42 | return endTime-startTime; 43 | } 44 | } 45 | 46 | private static ThreadPool pool; 47 | 48 | protected AsyncWork(WorkDetail work) 49 | { 50 | this.work = work; 51 | this.priority = 0; 52 | } 53 | 54 | protected void run() 55 | { 56 | 57 | this.startTime = get_monotonic_time(); 58 | this.work(); 59 | this.endTime = get_monotonic_time(); 60 | } 61 | 62 | public async void execute() 63 | { 64 | pool.add(this); 65 | } 66 | 67 | public async void executePri(int priority) 68 | { 69 | this.priority = priority; 70 | this.execute(); 71 | } 72 | 73 | /** 74 | * 75 | * 静态方法创建任务 76 | * 77 | */ 78 | public static AsyncWork create(WorkDetail work){ 79 | return new AsyncWork(work); 80 | } 81 | 82 | public static void createThreadPool(int maxThreads) throws ThreadError 83 | { 84 | pool = new ThreadPool.with_owned_data((work)=> { work.run();},3,true); 85 | 86 | //设置任务优先级 87 | pool.set_sort_function((w1,w2)=>{ 88 | return (w1.priority < w2.priority) 89 | ?-1 90 | :(int)(w1.priority > w2.priority); 91 | }); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /lib/config/constant.vala: -------------------------------------------------------------------------------- 1 | namespace Constant 2 | { 3 | 4 | public const string TYPE = "type"; 5 | public const string USER = "user"; 6 | public const string UUID = "uuid"; 7 | public const string NAME = "name"; 8 | public const string HOST = "host"; 9 | public const string PORT = "port"; 10 | public const string COMMENT ="comment"; 11 | public const string MAX_WAIT = "maxWait"; 12 | public const string MAX_SIZE = "maxSize"; 13 | public const string PASSWORD = "password"; 14 | public const string DATABASE = "database"; 15 | public const string AUTH_MODEL = "authModel"; 16 | public const string DB_FILE_PATH = "dbFilePath"; 17 | //保存模式 18 | public const string SAVE_MODEL = "saveModel"; 19 | //空值字符串 20 | public const string NULL_SYMBOL = "\\+//NULL\\*//"; 21 | } 22 | -------------------------------------------------------------------------------- /lib/config/datasource.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 枚举当前支持认证方式 4 | * 5 | */ 6 | public enum AuthModel 7 | { 8 | /** 9 | * 10 | * 用户名/密码认证 11 | * 12 | */ 13 | USER_PASSWORD, 14 | 15 | /** 16 | * 17 | * 18 | * Window系统帐号认证 19 | * 20 | **/ 21 | WINDOW_CREDENTIALS, 22 | /** 23 | * 24 | * 无认证方式 25 | */ 26 | NONE 27 | } 28 | 29 | /** 30 | * 31 | * 数据源保存方式 32 | * 33 | */ 34 | public enum SaveModel 35 | { 36 | /** 37 | * 38 | * 39 | * 持久化保存 40 | * 41 | **/ 42 | FOREVER, 43 | /** 44 | * 45 | * 46 | * 不持久化保存 47 | * 48 | * 49 | **/ 50 | NEVER 51 | } 52 | 53 | /** 54 | * 55 | * 数据源配置 56 | * 57 | */ 58 | public class DataSource 59 | { 60 | /** 61 | * 62 | * 63 | * 数据源唯一id 64 | * 65 | **/ 66 | public string uuid; 67 | /** 68 | * 69 | * 70 | * 数据源名称 71 | * 72 | * 73 | **/ 74 | public string name; 75 | /** 76 | * 77 | * 78 | * 数据源备注 79 | * 80 | * 81 | **/ 82 | public string comment; 83 | /** 84 | * 85 | * 数据库服务器主机 86 | * 87 | */ 88 | public string host; 89 | 90 | /** 91 | * 92 | * 数据库服务器端口 93 | * 94 | */ 95 | public int port; 96 | 97 | /** 98 | * 99 | * 连接池最大尺寸 100 | * 101 | */ 102 | public int maxSize; 103 | 104 | /** 105 | * 106 | * 获取连接最大等待时间(秒) 107 | * 108 | */ 109 | public int maxWait; 110 | 111 | /** 112 | * 113 | * 认证用户名 114 | * 115 | */ 116 | public string user; 117 | 118 | /** 119 | * 120 | * 认证密码 121 | * 122 | */ 123 | public string password; 124 | 125 | /** 126 | * 127 | * 认证方式 128 | * 129 | */ 130 | public AuthModel authModel; 131 | 132 | /** 133 | * 134 | * 135 | * 数据库 136 | * 137 | * 138 | **/ 139 | public string database; 140 | 141 | /** 142 | * 143 | * 144 | * 保存模式 145 | * 146 | **/ 147 | public SaveModel saveModel; 148 | 149 | /** 150 | * 151 | * 当前数据库类型 152 | * 153 | */ 154 | public DatabaseType dbType; 155 | 156 | /** 157 | * 158 | * 159 | * 数据库文件路径 160 | * 161 | */ 162 | public string dbFilePath; 163 | 164 | public DataSource(DatabaseType type){ 165 | this.dbType = type; 166 | } 167 | 168 | public string toString(){ 169 | return """ 170 | { 171 | "type:%d, 172 | "host":"%s", 173 | "port":"%d", 174 | "user":"%s", 175 | "password":"%s" 176 | } 177 | """.printf(dbType,host,port,user,password); 178 | } 179 | 180 | public static DataSource default(DatabaseType type) 181 | { 182 | var dataSource = new DataSource(type); 183 | 184 | dataSource.port = 80; 185 | dataSource.name = ""; 186 | dataSource.user = ""; 187 | dataSource.comment = ""; 188 | dataSource.database = ""; 189 | dataSource.password = ""; 190 | dataSource.dbFilePath = ""; 191 | dataSource.host = "0.0.0.0"; 192 | dataSource.authModel = AuthModel.NONE; 193 | dataSource.uuid = Uuid.string_random(); 194 | dataSource.saveModel = SaveModel.FOREVER; 195 | 196 | return dataSource; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/config/db-feature.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 枚举数据库类型 4 | * 5 | */ 6 | public enum DatabaseType 7 | { 8 | MYSQL, 9 | SQLITE, 10 | ORACLE 11 | } 12 | 13 | /** 14 | * 15 | * 16 | * 配置当前支持数据库特征信息 17 | * 18 | * 19 | */ 20 | public class DatabaseFeature : Object { 21 | 22 | //数据库名称 23 | public string name 24 | { 25 | private set; 26 | get; 27 | } 28 | 29 | //数据库图标 30 | public string icon 31 | { 32 | private set; 33 | get; 34 | } 35 | 36 | //当前数据库类型 37 | public DatabaseType dbType 38 | { 39 | private set; 40 | get; 41 | } 42 | 43 | //当前版本是否支持 44 | public bool impl 45 | { 46 | private set; 47 | get; 48 | } 49 | 50 | public DatabaseFeature(string name,string icon,DatabaseType type,bool impl) 51 | { 52 | this.impl = impl; 53 | this.name = name; 54 | this.icon = icon; 55 | this.dbType = type; 56 | } 57 | 58 | private static DatabaseFeature[] features = null; 59 | 60 | public static unowned DatabaseFeature[] getFeatures() 61 | { 62 | if(features == null ) 63 | { 64 | features = 65 | { 66 | new DatabaseFeature("MySQL","dbfx-mysql",DatabaseType.MYSQL,true), 67 | new DatabaseFeature("SQLite","dbfx-sqlite",DatabaseType.SQLITE,true) 68 | }; 69 | } 70 | return features; 71 | } 72 | 73 | public static unowned DatabaseFeature? getFeature(DatabaseType type) 74 | { 75 | foreach(unowned var feature in getFeatures()) 76 | { 77 | if(feature.dbType == type) 78 | { 79 | return feature; 80 | } 81 | } 82 | 83 | return null; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /lib/config/error-domain.vala: -------------------------------------------------------------------------------- 1 | public errordomain FXError 2 | { 3 | ERROR 4 | } 5 | -------------------------------------------------------------------------------- /lib/config/table-column-type.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 表列类型 4 | * 5 | */ 6 | public enum TTableColumn 7 | { 8 | /** 9 | * 10 | * 数值型 11 | * 12 | **/ 13 | NUMBER, 14 | /** 15 | * 16 | * 日期类型 17 | * 18 | **/ 19 | DATE, 20 | /** 21 | * 22 | * 时间日期型 23 | * 24 | **/ 25 | DATE_TIME, 26 | /** 27 | * 28 | * 字符串类型 29 | * 30 | **/ 31 | STRING, 32 | /** 33 | * 34 | * 35 | * 二进制类型 36 | * 37 | */ 38 | BINARY, 39 | /** 40 | * 41 | * JSON类型 42 | * 43 | */ 44 | JSON 45 | } -------------------------------------------------------------------------------- /lib/connection-pool.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | 4 | /** 5 | * 6 | * 数据库连接池封装 7 | * 8 | */ 9 | public class SqlConnectionPool 10 | { 11 | /** 12 | * 13 | * 空闲队列 14 | * 15 | */ 16 | private ArrayQueue freeQueue; 17 | 18 | /** 19 | * 20 | * 工作队列 21 | * 22 | */ 23 | private ArrayQueue workQueue; 24 | /** 25 | * 26 | * Mutlip thread lock 27 | * 28 | */ 29 | private Object mutex; 30 | 31 | /** 32 | * 33 | * 数据源 34 | * 35 | */ 36 | private DataSource dataSource; 37 | 38 | 39 | private bool initCapacity; 40 | 41 | 42 | public SqlConnectionPool(DataSource dataSource) throws FXError 43 | { 44 | this.mutex = new Object(); 45 | this.initCapacity = false; 46 | this.dataSource = dataSource; 47 | this.freeQueue = new ArrayQueue(this.equal); 48 | this.workQueue = new ArrayQueue(this.equal); 49 | //初始化扩容 50 | this.capacity(); 51 | } 52 | 53 | /** 54 | * 55 | * 简单比较队列中两个对象是否相等 56 | * 57 | */ 58 | private bool equal(SqlConnection a,SqlConnection b){ 59 | return a==b; 60 | } 61 | 62 | 63 | public SqlConnection getConnection() throws FXError { 64 | SqlConnection con = null; 65 | var thread = Thread.self(); 66 | var startTime = get_real_time(); 67 | var maxWait =get_real_time() + this.dataSource.maxWait * 1000; 68 | while(true) 69 | { 70 | con = this.getConnection0(); 71 | //判断是否已经获取连接或者连接超时 72 | if(con != null || ( get_real_time() > maxWait)){ 73 | break; 74 | } 75 | 76 | thread.usleep(500); 77 | } 78 | 79 | if(con == null) 80 | { 81 | throw new FXError.ERROR(_("Not free connection")); 82 | } 83 | 84 | return con; 85 | } 86 | 87 | private SqlConnection? getConnection0(){ 88 | lock(mutex){ 89 | //从尾部获取连接对象,增加连接复用概率 90 | var con = this.freeQueue.poll_tail(); 91 | //成功获取到连接,添加到工作队列中 92 | if(con != null){ 93 | this.workQueue.add(con); 94 | } 95 | return con; 96 | } 97 | } 98 | 99 | /** 100 | * 101 | * 归还连接 102 | * 103 | */ 104 | public void back(SqlConnection con){ 105 | lock(mutex){ 106 | var exist = this.workQueue.remove(con); 107 | if(!exist){ 108 | warning("Connection already back?"); 109 | return; 110 | } 111 | //将连接重新放入队列 112 | this.freeQueue.add(con); 113 | } 114 | } 115 | 116 | /** 117 | * 118 | * 119 | * 清除连接池缓存连接 120 | * 121 | **/ 122 | public void shutdown() 123 | { 124 | this.workQueue.clear(); 125 | this.freeQueue.clear(); 126 | } 127 | 128 | private SqlConnectionPool capacity() throws FXError 129 | { 130 | if(!this.initCapacity) 131 | { 132 | var type = this.dataSource.dbType; 133 | 134 | unowned var instance = DatabaseFeature.getFeature(type); 135 | 136 | if(!instance.impl) 137 | { 138 | throw new FXError.ERROR(_("Not support")); 139 | } 140 | 141 | var maxSize = this.dataSource.maxSize; 142 | 143 | for(var i= 0 ; i < maxSize ; i++){ 144 | SqlConnection con = null; 145 | 146 | //初始化MYSQL连接 147 | if(type == DatabaseType.MYSQL) 148 | { 149 | con = new MysqlConnection(this.dataSource,this); 150 | } 151 | 152 | //初始化Sqlite连接 153 | if(type == DatabaseType.SQLITE) 154 | { 155 | con = new SqliteConnection(this.dataSource,this); 156 | } 157 | 158 | this.freeQueue.add(con); 159 | } 160 | 161 | this.initCapacity = true; 162 | } 163 | 164 | return this; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/connection.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 4 | * 数据库连接封装 5 | * 6 | */ 7 | public abstract class SqlConnection 8 | { 9 | /** 10 | * 11 | * 数据库连接池 12 | * 13 | */ 14 | protected SqlConnectionPool? pool{ 15 | set; 16 | get; 17 | } 18 | 19 | /** 20 | * 21 | * 判断当前连接是否已经连接 22 | * 23 | */ 24 | protected bool active{ 25 | set; 26 | get; 27 | } 28 | 29 | /** 30 | * 31 | * 当前数据源属性 32 | * 33 | **/ 34 | public DataSource dataSource{ 35 | private set; 36 | get; 37 | } 38 | 39 | protected SqlConnection(DataSource dataSource,SqlConnectionPool? pool) 40 | { 41 | this.pool = pool; 42 | this.dataSource = dataSource; 43 | } 44 | 45 | /** 46 | * 47 | * 连接到指定数据库 48 | * 49 | */ 50 | public abstract void connect() throws FXError; 51 | 52 | 53 | /** 54 | * 55 | * 获取服务器信息 56 | * 57 | */ 58 | public abstract DatabaseInfo serverInfo() throws FXError; 59 | 60 | 61 | /** 62 | * 63 | * 获取数据库schema列表 64 | * 65 | * 66 | * */ 67 | public abstract Gee.List schemas() throws FXError; 68 | 69 | 70 | /** 71 | * 72 | * 73 | * 获取某个Schema下的表列表 74 | * 75 | **/ 76 | public abstract Gee.List tables(string schema,bool view) throws FXError; 77 | 78 | /** 79 | * 80 | * 获取某张表列信息 81 | * 82 | **/ 83 | public abstract Gee.List tableColumns(string schema,string name) throws FXError; 84 | 85 | /** 86 | * 87 | * 分页查询数据 88 | * 89 | **/ 90 | public abstract Gee.List pageQuery(PageQuery query) throws FXError; 91 | 92 | /** 93 | * 94 | * 统计某张表数据条数 95 | * 96 | */ 97 | public abstract int64 pageCount(PageQuery query) throws FXError; 98 | 99 | 100 | /** 101 | * 102 | * 获取表定义语句 103 | * 104 | */ 105 | public abstract string ddl(string schema,string table,bool view) throws FXError; 106 | 107 | /** 108 | * 109 | * 关闭当前连接(放回连接池中) 110 | * 111 | */ 112 | public void close(){ 113 | if( this.pool == null ) 114 | { 115 | return; 116 | } 117 | this.pool.back(this); 118 | } 119 | 120 | /** 121 | * 122 | * 关闭当前连接 123 | * 124 | */ 125 | public abstract void shutdown(); 126 | } 127 | -------------------------------------------------------------------------------- /lib/impl/mysql-connection.vala: -------------------------------------------------------------------------------- 1 | using Mysql; 2 | 3 | public class MysqlConnection : SqlConnection 4 | { 5 | private Database database; 6 | 7 | private DataSource dataSource; 8 | 9 | public MysqlConnection(DataSource dataSource,SqlConnectionPool? pool) 10 | { 11 | base(dataSource,pool); 12 | this.dataSource = dataSource; 13 | this.database = new Database(); 14 | } 15 | 16 | public MysqlConnection.without(DataSource dataSource) 17 | { 18 | this(dataSource,null); 19 | } 20 | 21 | 22 | public override void connect() throws FXError 23 | { 24 | if(this.active) 25 | { 26 | return; 27 | } 28 | 29 | string user = null; 30 | string password = null; 31 | 32 | if(this.dataSource.authModel == AuthModel.USER_PASSWORD) 33 | { 34 | user = this.dataSource.user; 35 | password = this.dataSource.password; 36 | } 37 | 38 | var success = this.database.real_connect( 39 | this.dataSource.host, 40 | user, 41 | password, 42 | null, 43 | this.dataSource.port, 44 | null, 45 | 0 46 | ); 47 | //数据库连接失败->抛出异常 48 | if(!success) 49 | { 50 | throw new FXError.ERROR(this.database.error()); 51 | } 52 | 53 | this.active = true; 54 | } 55 | 56 | public override DatabaseInfo serverInfo() throws FXError 57 | { 58 | this.execute("SHOW VARIABLES"); 59 | 60 | string[] rows; 61 | var info = new DatabaseInfo(); 62 | var rs = this.database.use_result(); 63 | 64 | while((rows=rs.fetch_row())!=null){ 65 | var field = rows[0]; 66 | var value = rows[1]; 67 | if(field == "version"){ 68 | info.version = value; 69 | } 70 | if(field == "version_comment"){ 71 | info.name = value; 72 | } 73 | } 74 | return info; 75 | } 76 | 77 | public override Gee.List schemas() throws FXError 78 | { 79 | var sql = "SELECT * FROM information_schema.SCHEMATA"; 80 | 81 | this.execute(sql); 82 | 83 | string[] rows; 84 | var rs = this.database.use_result(); 85 | var list = new Gee.ArrayList(); 86 | while((rows = rs.fetch_row())!=null) 87 | { 88 | var item = new DatabaseSchema(); 89 | item.name = rows[1]; 90 | item.charset = rows[2]; 91 | item.collation = rows[3]; 92 | list.add(item); 93 | } 94 | return list; 95 | } 96 | 97 | public override Gee.List tables(string schema,bool view) throws FXError 98 | { 99 | string sql; 100 | 101 | if(view) 102 | { 103 | sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' ORDER BY `TABLE_NAME` ASC"; 104 | } 105 | else 106 | { 107 | sql = "SELECT `TABLE_NAME` FROM information_schema.TABLES WHERE `TABLE_SCHEMA`='%s' ORDER BY `TABLE_NAME` ASC"; 108 | } 109 | 110 | sql = sql.printf(schema); 111 | 112 | this.execute(sql); 113 | 114 | var rs = this.database.use_result(); 115 | var list = new Gee.ArrayList(); 116 | 117 | string[] rows; 118 | while((rows = rs.fetch_row())!=null ) 119 | { 120 | var table = new TableInfo(); 121 | table.name = rows[0]; 122 | table.tableType = view? TableType.VIEW : TableType.BASE_TABLE; 123 | 124 | list.add(table); 125 | } 126 | 127 | return list; 128 | } 129 | 130 | 131 | public override Gee.List tableColumns(string schema,string name) throws FXError 132 | { 133 | var sql = """SELECT 134 | `COLUMN_NAME`, 135 | `IS_NULLABLE`, 136 | `DATA_TYPE`, 137 | `COLUMN_DEFAULT` 138 | FROM 139 | information_schema.COLUMNS 140 | WHERE 141 | `TABLE_SCHEMA`='%s' 142 | AND 143 | `TABLE_NAME`='%s' 144 | ORDER BY 145 | `ORDINAL_POSITION` 146 | ASC"""; 147 | sql = sql.printf(schema,name); 148 | 149 | this.execute(sql); 150 | 151 | string[] rows; 152 | var rs = this.database.use_result(); 153 | var list = new Gee.ArrayList(); 154 | while((rows = rs.fetch_row()) != null) 155 | { 156 | 157 | var meta = new TableColumnMeta(); 158 | 159 | meta.name = rows[0]; 160 | meta.isNull = rows[1]=="YES"; 161 | meta.originType = rows[2]; 162 | 163 | list.add(meta); 164 | } 165 | 166 | return list; 167 | } 168 | 169 | 170 | /** 171 | * 172 | * 分页查询数据 173 | * 174 | **/ 175 | public override Gee.List pageQuery(PageQuery query) throws FXError 176 | { 177 | this.connect(); 178 | 179 | var size =query.size; 180 | var page = query.page; 181 | var sort = query.sort; 182 | var where = query.where; 183 | var table = query.table; 184 | var schema = query.schema; 185 | 186 | var offset = (page - 1) * size; 187 | 188 | var sql = @"SELECT * FROM $schema.$table $where $sort LIMIT $offset,$size"; 189 | 190 | this.execute(sql); 191 | 192 | string[] rows; 193 | var rs = this.database.use_result(); 194 | var list = new Gee.ArrayList(); 195 | while((rows=rs.fetch_row()) != null) 196 | { 197 | foreach (var item in rows) 198 | { 199 | 200 | list.add(item == null ? Constant.NULL_SYMBOL : item); 201 | } 202 | } 203 | 204 | GLib.debug(query.where); 205 | 206 | return list; 207 | } 208 | 209 | /** 210 | * 211 | * 统计某张表数据条数 212 | * 213 | */ 214 | public override int64 pageCount(PageQuery query) throws FXError 215 | { 216 | var table = query.table; 217 | var schema = query.schema; 218 | 219 | var sql = "SELECT COUNT(*) FROM %s.%s %s".printf(query.schema,query.table, query.where); 220 | 221 | this.execute(sql); 222 | 223 | var rs = this.database.use_result(); 224 | var rows = rs.fetch_row(); 225 | if(rows.length == 0) 226 | { 227 | return 0; 228 | } 229 | return int64.parse(rows[0]); 230 | } 231 | 232 | 233 | public override string ddl(string schema,string table,bool view) throws FXError 234 | { 235 | var sql = @"SHOW CREATE TABLE `$schema`.`$table`"; 236 | 237 | this.execute(sql); 238 | 239 | var rs = this.database.use_result(); 240 | 241 | var row = rs.fetch_row(); 242 | 243 | return row[1]; 244 | } 245 | 246 | 247 | public override void shutdown() 248 | { 249 | // 250 | // No need manual shutdown connection 251 | // 252 | } 253 | 254 | private void execute(string sql) throws FXError 255 | { 256 | this.connect(); 257 | 258 | GLib.debug("Mysql:Execute sql:[%s]".printf(sql)); 259 | 260 | if(this.database.query(sql) == 0) 261 | { 262 | return; 263 | } 264 | 265 | var errmsg = this.database.error(); 266 | 267 | warning("Mysql:Sql execute error:SQL:[%s],ERR:[%s]".printf(sql,errmsg)); 268 | 269 | throw new FXError.ERROR(errmsg); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /lib/impl/sqlite-connection.vala: -------------------------------------------------------------------------------- 1 | using Sqlite; 2 | 3 | public class SqliteConnection : SqlConnection 4 | { 5 | private Database database; 6 | 7 | private DataSource dataSource; 8 | 9 | public SqliteConnection(DataSource dataSource,SqlConnectionPool? pool) 10 | { 11 | base(dataSource,pool); 12 | this.database = null; 13 | this.dataSource = dataSource; 14 | } 15 | 16 | public SqliteConnection.without(DataSource dataSource) 17 | { 18 | this(dataSource,null); 19 | } 20 | /** 21 | * 22 | * 连接到指定数据库 23 | * 24 | */ 25 | public override void connect() throws FXError 26 | { 27 | if(this.active) 28 | { 29 | return; 30 | } 31 | 32 | var code = Database.open_v2(this.dataSource.dbFilePath,out this.database,OPEN_READWRITE); 33 | 34 | if(code != OK) 35 | { 36 | warning("Open sqlite database fail:%s".printf(this.database.errmsg())); 37 | 38 | throw new FXError.ERROR(this.database.errmsg()); 39 | } 40 | 41 | this.active = true; 42 | } 43 | 44 | 45 | /** 46 | * 47 | * 获取服务器信息 48 | * 49 | */ 50 | public override DatabaseInfo serverInfo() throws FXError 51 | { 52 | var info = new DatabaseInfo(); 53 | 54 | info.name = "sqlite"; 55 | info.version = "3.0"; 56 | 57 | return info; 58 | } 59 | 60 | 61 | /** 62 | * 63 | * 获取数据库schema列表 64 | * 65 | * 66 | * */ 67 | public override Gee.List schemas() throws FXError 68 | { 69 | var list = new Gee.ArrayList(); 70 | var schema = new DatabaseSchema(); 71 | 72 | schema.name = "main"; 73 | schema.charset = "utf8"; 74 | schema.collation = "utf8bin"; 75 | 76 | list.add(schema); 77 | 78 | return list; 79 | } 80 | 81 | 82 | /** 83 | * 84 | * 85 | * 获取某个Schema下的表列表 86 | * 87 | **/ 88 | public override Gee.List tables(string schema,bool view) throws FXError 89 | { 90 | 91 | 92 | var sql = "select name from sqlite_master where type =$table order by name"; 93 | 94 | var stmt = this.execute(sql); 95 | 96 | //预编译 97 | var index =stmt.bind_parameter_index("$table"); 98 | 99 | //绑定参数 100 | stmt.bind_text(index,view ? "view" : "table"); 101 | 102 | var cols = stmt.column_count(); 103 | 104 | var list = new Gee.ArrayList(); 105 | 106 | while(stmt.step() == Sqlite.ROW) 107 | { 108 | var table = new TableInfo(); 109 | 110 | for (int i = 0; i < cols; i++) 111 | { 112 | var col_name = stmt.column_name (i) ?? ""; 113 | var val = stmt.column_text (i) ?? ""; 114 | var type_id = stmt.column_type (i); 115 | 116 | if(col_name == "name") 117 | { 118 | table.name = val; 119 | } 120 | 121 | } 122 | list.add(table); 123 | } 124 | return list; 125 | } 126 | 127 | /** 128 | * 129 | * 获取某张表列信息 130 | * 131 | **/ 132 | public override Gee.List tableColumns(string schema,string name) throws FXError 133 | { 134 | 135 | var sql = @"PRAGMA table_info($name)"; 136 | 137 | var stmt = this.execute(sql); 138 | var cols = stmt.column_count(); 139 | 140 | var list = new Gee.ArrayList(); 141 | 142 | while(stmt.step() == Sqlite.ROW) 143 | { 144 | var columnMeta = new TableColumnMeta(); 145 | 146 | for (int i = 0; i < cols; i++) 147 | { 148 | var column = stmt.column_name(i); 149 | if(column == "name") 150 | { 151 | columnMeta.name = stmt.column_text(i)??Constant.NULL_SYMBOL; 152 | } 153 | if(column == "notnull") 154 | { 155 | columnMeta.isNull = (stmt.column_int(i) == 1); 156 | } 157 | if(column == "type") 158 | { 159 | columnMeta.originType = stmt.column_text(i); 160 | } 161 | } 162 | 163 | list.add(columnMeta); 164 | } 165 | 166 | return list; 167 | } 168 | 169 | /** 170 | * 171 | * 分页查询数据 172 | * 173 | **/ 174 | public override Gee.List pageQuery(PageQuery query) throws FXError 175 | { 176 | var size = query.size; 177 | var offset = (query.page - 1) * size; 178 | 179 | var sql = "SELECT * FROM %s %s %s LIMIT $size offset $offset".printf(query.table,query.where,query.sort); 180 | 181 | var stmt = this.execute(sql); 182 | 183 | var j = stmt.bind_parameter_index("$size"); 184 | var k = stmt.bind_parameter_index("$offset"); 185 | 186 | stmt.bind_int(j,size); 187 | stmt.bind_int(k,offset); 188 | 189 | var cols = stmt.column_count(); 190 | var list = new Gee.ArrayList(); 191 | 192 | while(stmt.step() == ROW) 193 | { 194 | for(var l = 0;l < cols ; l++) 195 | { 196 | list.add(stmt.column_text(l)); 197 | } 198 | } 199 | 200 | return list; 201 | } 202 | 203 | /** 204 | * 205 | * 统计某张表数据条数 206 | * 207 | */ 208 | public override int64 pageCount(PageQuery query) throws FXError 209 | { 210 | 211 | var sql = "SELECT COUNT(*) FROM %s %s".printf(query.table,query.where); 212 | 213 | var stmt = this.execute(sql); 214 | 215 | int64 count = 0; 216 | if(stmt.step() == ROW) 217 | { 218 | count = stmt.column_int(0); 219 | } 220 | 221 | return count; 222 | } 223 | 224 | /** 225 | * 226 | * 关闭当前连接(放回连接池中) 227 | * 228 | */ 229 | public void close(){ 230 | if( this.pool == null ) 231 | { 232 | return; 233 | } 234 | this.pool.back(this); 235 | } 236 | 237 | public override string ddl(string schema,string table,bool view) throws FXError 238 | { 239 | var sql = "SELECT sql FROM sqlite_master WHERE type=$type AND tbl_name=$name"; 240 | 241 | var stmt = this.execute(sql); 242 | 243 | var i = stmt.bind_parameter_index("$type"); 244 | var j = stmt.bind_parameter_index("$name"); 245 | 246 | stmt.bind_text(j,table); 247 | stmt.bind_text(i,view?"view":"table"); 248 | 249 | var ddl = ""; 250 | 251 | var cols = stmt.column_count(); 252 | 253 | if(stmt.step() == ROW) 254 | { 255 | for(var k = 0 ; k < cols ; k++) 256 | { 257 | ddl = stmt.column_text(k); 258 | } 259 | } 260 | 261 | return ddl; 262 | } 263 | 264 | private Statement execute(string sql) throws FXError 265 | { 266 | this.connect(); 267 | 268 | Statement stmt; 269 | 270 | var code = this.database.prepare_v2(sql,sql.length,out stmt); 271 | 272 | debug("Sqlite:%s".printf(sql)); 273 | 274 | if(code == OK) 275 | { 276 | return stmt; 277 | } 278 | 279 | var errmsg = this.database.errmsg(); 280 | 281 | warning("Sqlite:Execute sql fail:[%s],errmsg:[%s]".printf(sql,errmsg)); 282 | 283 | throw new FXError.ERROR(errmsg); 284 | } 285 | 286 | /** 287 | * 288 | * 关闭当前连接 289 | * 290 | */ 291 | public override void shutdown() 292 | { 293 | 294 | } 295 | } -------------------------------------------------------------------------------- /lib/meson.build: -------------------------------------------------------------------------------- 1 | libdbfx_sources = [ 2 | 'connection-pool.vala', 3 | 'connection.vala', 4 | 'util/str-util.vala', 5 | 'model/page-query.vala', 6 | 'config/constant.vala', 7 | 'async/async-work.vala', 8 | 'config/datasource.vala', 9 | 'model/table-info.vala', 10 | 'config/db-feature.vala', 11 | 'config/error-domain.vala', 12 | 'model/database-info.vala', 13 | 'model/table-column.vala', 14 | 'model/database-schema.vala', 15 | 'impl/mysql-connection.vala', 16 | 'impl/sqlite-connection.vala', 17 | 'config/table-column-type.vala' 18 | ] 19 | 20 | libdbfx_c_args = [ 21 | '-include','config.h' 22 | ] 23 | 24 | libdbfx_deps = [ 25 | glib, 26 | gio, 27 | libgee, 28 | sqlite3, 29 | libmariadb 30 | ] 31 | 32 | #静态链接 33 | libdbfx = static_library('dbfx',libdbfx_sources, 34 | include_directories: conf_include_dir, 35 | c_args: libdbfx_c_args, 36 | dependencies: libdbfx_deps 37 | ) 38 | 39 | # 声明依赖提供给其他模块使用 40 | libdbfx_dep = declare_dependency( 41 | link_with: libdbfx, 42 | include_directories: include_directories('.'), 43 | ) 44 | -------------------------------------------------------------------------------- /lib/model/database-info.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 数据库信息 4 | * 5 | */ 6 | public class DatabaseInfo : Object { 7 | 8 | /** 9 | * 10 | * 数据库名称 11 | * 12 | */ 13 | public string name; 14 | 15 | /** 16 | * 17 | * 数据库版本 18 | * 19 | */ 20 | public string version; 21 | 22 | public DatabaseInfo () 23 | { 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/model/database-schema.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 4 | * 数据库Scheme 5 | * 6 | * */ 7 | public class DatabaseSchema 8 | { 9 | /** 10 | * 11 | * 12 | * Scheme名称 13 | * 14 | * */ 15 | public string name; 16 | /** 17 | * 18 | * 字符集 19 | * 20 | * */ 21 | public string charset; 22 | 23 | /** 24 | * 25 | * 26 | * 字符排序 27 | * 28 | * */ 29 | public string collation{ 30 | set; 31 | get; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/model/page-query.vala: -------------------------------------------------------------------------------- 1 | public class PageQuery : Object 2 | { 3 | /** 4 | * 5 | * 当前页数 6 | * 7 | */ 8 | public int page; 9 | /** 10 | * 11 | * 当前页尺寸 12 | * 13 | */ 14 | public int size; 15 | /** 16 | * 17 | * schema名 18 | * 19 | */ 20 | public string schema{ 21 | private set; 22 | get; 23 | } 24 | 25 | /** 26 | * 27 | * 表名 28 | * 29 | */ 30 | public string table{ 31 | private set; 32 | get; 33 | } 34 | 35 | /** 36 | * 37 | * 自定义查询条件 38 | * 39 | */ 40 | public string where; 41 | 42 | /** 43 | * 44 | * 自定义排序方式 45 | * 46 | **/ 47 | public string sort; 48 | 49 | 50 | public PageQuery(string schema,string table) 51 | { 52 | this.page = 0; 53 | this.size = 500; 54 | this.sort = ""; 55 | this.where = ""; 56 | 57 | this.table = table; 58 | this.schema = schema; 59 | } 60 | 61 | /** 62 | * 63 | * 64 | * 检查where和sort字段是否发生改变,如果发生改变将当前页系数重置到1 65 | * 66 | */ 67 | public bool inspect(int page,string where,string sort) 68 | { 69 | var s1 = sort.strip(); 70 | var s2 = where.strip(); 71 | 72 | var reset = false; 73 | 74 | reset = (this.where != s2); 75 | 76 | if(!reset) 77 | { 78 | reset = (this.sort != s1); 79 | } 80 | 81 | if(reset) 82 | { 83 | this.page = 1; 84 | } 85 | else 86 | { 87 | this.page = page; 88 | } 89 | 90 | this.sort = s1; 91 | this.where = s2; 92 | 93 | return reset; 94 | } 95 | } -------------------------------------------------------------------------------- /lib/model/table-column.vala: -------------------------------------------------------------------------------- 1 | public class TableColumnMeta 2 | { 3 | /** 4 | * 5 | * 列名 6 | * 7 | **/ 8 | public string name; 9 | /* 10 | * 11 | * 是否可为空 12 | * 13 | **/ 14 | public bool isNull; 15 | 16 | /** 17 | * 18 | * 原始类型 19 | * 20 | **/ 21 | public string originType; 22 | 23 | 24 | /** 25 | * 26 | * 创建空列 27 | * 28 | */ 29 | public TableColumnMeta.empty() 30 | { 31 | this.name = ""; 32 | this.isNull = false; 33 | } 34 | } -------------------------------------------------------------------------------- /lib/model/table-info.vala: -------------------------------------------------------------------------------- 1 | public enum TableType 2 | { 3 | /** 4 | * 5 | * 6 | * 基础表 7 | * 8 | **/ 9 | BASE_TABLE, 10 | /** 11 | * 12 | * 视图 13 | * 14 | **/ 15 | VIEW 16 | } 17 | public class TableInfo : GLib.Object 18 | { 19 | /* 20 | * 21 | * 表名 22 | * 23 | **/ 24 | public string name{ 25 | get; 26 | set; 27 | } 28 | /* 29 | * 30 | * 表类型 31 | * 32 | **/ 33 | public TableType tableType{ 34 | set; 35 | get; 36 | } 37 | } -------------------------------------------------------------------------------- /lib/util/str-util.vala: -------------------------------------------------------------------------------- 1 | public class StrUtil 2 | { 3 | /** 4 | * 5 | * 判断是否空白字符串 6 | * 7 | **/ 8 | public static bool isBlack(string? str) 9 | { 10 | return str == null || str.strip() == ""; 11 | } 12 | 13 | 14 | /** 15 | * 16 | * 17 | * 判断字符串是否不为空 18 | * 19 | */ 20 | public static bool isNotBlack(string? str) 21 | { 22 | return !isBlack(str); 23 | } 24 | } -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'ldbfx', ['c', 'vala'], 3 | version: '0.1.0', 4 | license: 'Apache-2.0', 5 | meson_version: '>= 0.50.0', 6 | default_options: [ 'warning_level=2'] 7 | ) 8 | 9 | datadir = get_option('datadir') 10 | 11 | i18n = import('i18n') 12 | cmake = import('cmake') 13 | 14 | conf_include_dir = include_directories('.') 15 | 16 | add_project_arguments( 17 | [ 18 | '--vapidir', join_paths(meson.current_source_dir(), 'vapi'), 19 | '--pkg','config' 20 | ], 21 | language: 'vala' 22 | ) 23 | 24 | 25 | min_glib_version = '2.50' 26 | 27 | 28 | libgee = dependency('gee-0.8') 29 | sqlite3 = dependency('sqlite3') 30 | libxml = dependency('libxml-2.0') 31 | libmariadb = dependency('libmariadb') 32 | libjson = dependency('json-glib-1.0') 33 | #libpg_query = dependency('libpg_query') 34 | gtk = dependency('gtk4', version: '>= 4.6.4') 35 | libsourceview = dependency('gtksourceview-5') 36 | gio = dependency('gio-2.0', version: '>=' + min_glib_version) 37 | glib = dependency('glib-2.0', version: '>=' + min_glib_version) 38 | 39 | # 项目配置信息 40 | conf = configuration_data() 41 | 42 | # 43 | # 当前系统类型 linux/window/macos 44 | # 45 | conf.set_quoted('SYSTEM_TYPE','linux') 46 | 47 | conf.set_quoted('APPLICATION_ID','cn.navclub.ldbfx') 48 | conf.set_quoted('VERSION', meson.project_version()) 49 | conf.set_quoted('PROJECT_NAME',meson.project_name()) 50 | conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) 51 | conf.set_quoted('ICON_SEARCH_PATH',join_paths(get_option('datadir'),'icons')) 52 | conf.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) 53 | 54 | 55 | #安装配置头文件 56 | configure_file(output: 'config.h', configuration: conf) 57 | 58 | 59 | meson.add_install_script('meson_post_install.py') 60 | 61 | subdir('data') 62 | subdir('lib') 63 | subdir('src') 64 | subdir('po') 65 | 66 | -------------------------------------------------------------------------------- /meson_post_install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from os import environ, path 4 | from subprocess import call 5 | 6 | prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') 7 | datadir = path.join(prefix, 'share') 8 | destdir = environ.get('DESTDIR', '') 9 | 10 | # Package managers set this so we don't need to run 11 | if not destdir: 12 | print('Updating icon cache...') 13 | call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')]) 14 | 15 | print('Updating desktop database...') 16 | call(['update-desktop-database', '-q', path.join(datadir, 'applications')]) 17 | 18 | print('Compiling GSettings schemas...') 19 | call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')]) 20 | 21 | 22 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | # Please keep that language sort 2 | zh_CN 3 | en_US 4 | -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | data/cn.navclub.dbfx.desktop.in 2 | data/cn.navclub.dbfx.appdata.xml.in 3 | data/cn.navclub.dbfx.gschema.xml 4 | src/window.ui 5 | src/main.vala 6 | src/window.vala 7 | -------------------------------------------------------------------------------- /po/en_US.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: navclub-tool\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2012-03-01 21:24+0100\n" 6 | "PO-Revision-Date: 2012-03-01 21:24+0100\n" 7 | "Last-Translator: Automatically generated\n" 8 | "Language-Team: none\n" 9 | "Language: ru\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 14 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 15 | 16 | msgid "dbfx" 17 | msgstr "dbfx" 18 | 19 | msgid "Welcome Page" 20 | msgstr "Welcome Page" 21 | 22 | msgid "Not support" 23 | msgstr "Current database not support!" 24 | 25 | msgid "devel title" 26 | msgstr "Current module developing...." 27 | 28 | msgid "New" 29 | msgstr "New" 30 | 31 | msgid "Save" 32 | msgstr "Save" 33 | 34 | msgid "Duplicate" 35 | msgstr "Duplicate" 36 | 37 | msgid "Cancel" 38 | msgstr "Cancel" 39 | 40 | msgid "Test Connect" 41 | msgstr "Test Connect" 42 | 43 | msgid "Not close connect" 44 | msgstr "Current connect manage by pool.Not allow manual close connect!" 45 | 46 | msgid "Save config fail" 47 | msgstr "Config persistence fail,more error info please view log file." 48 | 49 | msgid "Unknow data source" 50 | msgstr "Unknow data source" 51 | 52 | msgid "views" 53 | msgstr "views" 54 | 55 | msgid "tables" 56 | msgstr "tables" 57 | 58 | msgid "slogan" 59 | msgstr "v-dbfx A professional visual database management tool." 60 | 61 | msgid "_Setting" 62 | msgstr "Setting" 63 | 64 | msgid "_Exit" 65 | msgstr "Exit" 66 | 67 | msgid "_Ok" 68 | msgstr "Ok" 69 | 70 | msgid "_Next" 71 | msgstr "Next" 72 | 73 | msgid "_Cancel" 74 | msgstr "Cancel" 75 | 76 | msgid "_Test Connection" 77 | msgstr "Test Connection" 78 | 79 | msgid "_General" 80 | msgstr "General" 81 | 82 | msgid "_New Connection" 83 | msgstr "New Connection" 84 | 85 | msgid "_Back" 86 | msgstr "Back" 87 | 88 | msgid "_Delete warn" 89 | msgstr "Delete warn" 90 | 91 | msgid "_Delete db" 92 | msgstr "Are you sure delete current data source?" 93 | 94 | msgid "_Rows" 95 | msgstr "rows" 96 | 97 | msgid "_File" 98 | msgstr "File" 99 | 100 | msgid "_View" 101 | msgstr "View" 102 | 103 | msgid "_Full Screen" 104 | msgstr "Full Screen" 105 | 106 | msgid "_Select" 107 | msgstr "Select" 108 | 109 | msgid "_Open data source fail" 110 | msgstr "Open data source failed!" 111 | 112 | msgid "_Warn" 113 | msgstr "Wan" 114 | 115 | msgid "_Error" 116 | msgstr "Error" 117 | 118 | msgid "_Confirmation" 119 | msgstr "Confirmation" 120 | 121 | msgid "_Information" 122 | msgstr "Information" 123 | 124 | msgid "_Open File" 125 | msgstr "Open File" 126 | 127 | msgid "_New Terminal" 128 | msgstr "New Terminal" 129 | 130 | msgid "_Loading fail" 131 | msgstr "Loading data failed!" 132 | 133 | msgid "_DDL query error" 134 | msgstr "DDL info query fail!" 135 | 136 | msgid "_Copy" 137 | msgstr "Copy" 138 | 139 | msgid "_Loading" 140 | msgstr "Loading..." 141 | 142 | msgid "_Copy success" 143 | msgstr "Copy success" 144 | 145 | msgid "_Host" 146 | msgstr "Host" 147 | 148 | msgid "_Port" 149 | msgstr "Port" 150 | 151 | msgid "_User" 152 | msgstr "User" 153 | 154 | msgid "_Password" 155 | msgstr "Password" 156 | 157 | msgid "_Authorization" 158 | msgstr "Authorization" 159 | 160 | msgid "_Save" 161 | msgstr "Save" 162 | 163 | msgid "_Database" 164 | msgstr "Database" 165 | 166 | msgid "_Name" 167 | msgstr "Name" 168 | 169 | msgid "_Comment" 170 | msgstr "Comment" 171 | 172 | msgid "_Confirm info" 173 | msgstr "Confirm information" 174 | 175 | msgid "_Error info" 176 | msgstr "Error information" 177 | 178 | msgid "Warn info" 179 | msgstr "Warn information" 180 | 181 | msgid "_Exit confirm" 182 | msgstr "Are you sure exit current program?" -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext(meson.project_name(), 2 | preset: 'glib' 3 | ) 4 | -------------------------------------------------------------------------------- /po/zh_CN.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: navclub-tool\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2012-03-01 21:24+0100\n" 6 | "PO-Revision-Date: 2012-03-01 21:24+0100\n" 7 | "Last-Translator: Automatically generated\n" 8 | "Language-Team: none\n" 9 | "Language: ru\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 14 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 15 | 16 | msgid "dbfx" 17 | msgstr "dbfx" 18 | 19 | msgid "Welcome Page" 20 | msgstr "欢迎页" 21 | 22 | msgid "New" 23 | msgstr "新建" 24 | 25 | msgid "Duplicate" 26 | msgstr "复制" 27 | 28 | msgid "Not support" 29 | msgstr "暂不支持当前数据库!" 30 | 31 | msgid "devel title" 32 | msgstr "当前模块正在开发中...." 33 | 34 | 35 | msgid "Save" 36 | msgstr "保存" 37 | 38 | msgid "Cancel" 39 | msgstr "取消" 40 | 41 | msgid "Test Connect" 42 | msgstr "测试连接" 43 | 44 | msgid "Not close connect" 45 | msgstr "当前连接归连接池所有不允许手动关闭连接" 46 | 47 | msgid "Save config fail" 48 | msgstr "配置信息持久化失败,具体错误信息请查看日志文件。" 49 | 50 | msgid "Unknow data source" 51 | msgstr "未知数据源头" 52 | 53 | msgid "views" 54 | msgstr "视图" 55 | 56 | msgid "tables" 57 | msgstr "表" 58 | 59 | msgid "slogan" 60 | msgstr "v-dbfx 一款专业数据库可视化管理工具." 61 | 62 | 63 | msgid "_Setting" 64 | msgstr "设置" 65 | 66 | msgid "_Exit" 67 | msgstr "退出" 68 | 69 | msgid "_Ok" 70 | msgstr "确定" 71 | 72 | msgid "_Next" 73 | msgstr "下一步" 74 | 75 | msgid "_Test Connection" 76 | msgstr "测试连接" 77 | 78 | msgid "_General" 79 | msgstr "通用" 80 | 81 | msgid "_New Connection" 82 | msgstr "新建连接" 83 | 84 | msgid "_Back" 85 | msgstr "返回" 86 | 87 | msgid "_Cancel" 88 | msgstr "取消" 89 | 90 | msgid "_Delete warn" 91 | msgstr "删除警告" 92 | 93 | msgid "_Delete db" 94 | msgstr "你确定删除当前数据源?" 95 | 96 | msgid "_Rows" 97 | msgstr "行" 98 | 99 | msgid "_File" 100 | msgstr "文件" 101 | 102 | msgid "_View" 103 | msgstr "视图" 104 | 105 | msgid "_Full Screen" 106 | msgstr "全屏" 107 | 108 | msgid "_Select" 109 | msgstr "选择" 110 | 111 | msgid "_Open data source fail" 112 | msgstr "无法打开目标数据源头!" 113 | 114 | 115 | msgid "_Warn" 116 | msgstr "警告" 117 | 118 | msgid "_Error" 119 | msgstr "错误" 120 | 121 | msgid "_Confirmation" 122 | msgstr "确认" 123 | 124 | msgid "_Information" 125 | msgstr "信息" 126 | 127 | msgid "_Open File" 128 | msgstr "打开文件" 129 | 130 | msgid "_New Terminal" 131 | msgstr "新建终端" 132 | 133 | msgid "_Loading fail" 134 | msgstr "数据加载失败!" 135 | 136 | msgid "_DDL query error" 137 | msgstr "设计表信息查询失败!" 138 | 139 | msgid "_Copy" 140 | msgstr "复制" 141 | 142 | msgid "_Loading" 143 | msgstr "加载中..." 144 | 145 | msgid "_Copy success" 146 | msgstr "复制成功" 147 | 148 | msgid "_Host" 149 | msgstr "主机" 150 | 151 | msgid "_Port" 152 | msgstr "端口" 153 | 154 | msgid "_User" 155 | msgstr "用户" 156 | 157 | msgid "_Password" 158 | msgstr "密码" 159 | 160 | msgid "_Authorization" 161 | msgstr "认证" 162 | 163 | msgid "_Save" 164 | msgstr "保存" 165 | 166 | msgid "_Database" 167 | msgstr "数据库" 168 | 169 | msgid "_Name" 170 | msgstr "名称" 171 | 172 | msgid "_Comment" 173 | msgstr "备注" 174 | 175 | msgid "_Confirm info" 176 | msgstr "确认信息" 177 | 178 | msgid "_Error info" 179 | msgstr "错误信息" 180 | 181 | msgid "_Warn info" 182 | msgstr "警告信息" 183 | 184 | msgid "_Exit confirm" 185 | msgstr "你确定要退出当前程序?" -------------------------------------------------------------------------------- /src/config/app-config.vala: -------------------------------------------------------------------------------- 1 | using Environment; 2 | 3 | // 4 | // 数据库配置文件 5 | // 6 | const string dbPreference = "db-preference.json"; 7 | 8 | public class AppConfig 9 | { 10 | //缓存json数据 11 | private static Json.Node node; 12 | 13 | //APP配置文件路径 14 | private static string appDataFolder; 15 | 16 | //缓存数据源 17 | private static Gee.Map map; 18 | 19 | /** 20 | * 21 | * 22 | * 初始化应用数据目录 23 | * 24 | */ 25 | public static void initAppDataFolder() 26 | { 27 | map = new Gee.HashMap(); 28 | switch(SYSTEM_TYPE){ 29 | case "linux": 30 | case "macos": 31 | appDataFolder = get_home_dir()+"/."+PROJECT_NAME+"/"; 32 | break; 33 | case "window": 34 | appDataFolder = get_variable("APPDATA")+"\\"+PROJECT_NAME+"\\"; 35 | break; 36 | } 37 | debug("Current app data dir:"+appDataFolder); 38 | } 39 | 40 | /** 41 | * 42 | * 加载配置文件中的数据源 43 | * 44 | **/ 45 | public static Gee.Collection fetchDataSource() 46 | { 47 | var filename = "%s%s".printf(appDataFolder,dbPreference); 48 | 49 | var file = File.new_for_path(filename); 50 | 51 | var parser = new Json.Parser(); 52 | 53 | if(!file.query_exists()) 54 | { 55 | parser.load_from_data("[]"); 56 | } 57 | else 58 | { 59 | parser.load_from_stream(file.read()); 60 | } 61 | 62 | var array = (node = parser.get_root()).get_array(); 63 | 64 | foreach(var item in array.get_elements()) 65 | { 66 | var dataSource = create(item.get_object()); 67 | map.set(dataSource.uuid,dataSource); 68 | } 69 | 70 | return map.values; 71 | 72 | } 73 | 74 | /** 75 | * 76 | * 添加/更新数据源 77 | * 78 | */ 79 | public static void addDataSource(DataSource dataSource,bool update) throws FXError 80 | { 81 | var array = node.get_array(); 82 | var persistence = (dataSource.saveModel == SaveModel.FOREVER); 83 | if(persistence) 84 | { 85 | if(update) 86 | { 87 | foreach(var node in array.get_elements()) 88 | { 89 | var obj = node.get_object(); 90 | var uuid = obj.get_string_member(Constant.UUID); 91 | if(uuid == dataSource.uuid) 92 | { 93 | node.set_object(toJson(dataSource)); 94 | break; 95 | } 96 | } 97 | } 98 | else 99 | { 100 | array.add_object_element(toJson(dataSource)); 101 | } 102 | flush2Disk(); 103 | }else 104 | { 105 | deleteById(dataSource.uuid,false); 106 | } 107 | map.set(dataSource.uuid,dataSource); 108 | } 109 | 110 | /** 111 | * 112 | * 113 | * 删除数据源根据id 114 | * 115 | * 116 | **/ 117 | public static void deleteById(string uuid,bool all) 118 | { 119 | var j = 0; 120 | var i = -1; 121 | var array = node.get_array(); 122 | foreach(var node in array.get_elements()) 123 | { 124 | var obj = node.get_object(); 125 | if(obj.get_string_member(Constant.UUID) == uuid){ 126 | i = j; 127 | break; 128 | } 129 | ++j; 130 | } 131 | if(i != -1) 132 | { 133 | array.remove_element(i); 134 | } 135 | 136 | //From cached remove dataSource 137 | if(all) 138 | { 139 | map.remove(uuid); 140 | debug("Success remove data-source:[%s] from cached!".printf(uuid)); 141 | } 142 | 143 | flush2Disk(); 144 | 145 | if(i != -1) 146 | { 147 | debug("Success remove data-source:[%s] from disk!".printf(uuid)); 148 | } 149 | } 150 | 151 | /** 152 | * 153 | * 154 | * 刷新配置到文件中 155 | * 156 | * 157 | **/ 158 | private static void flush2Disk() 159 | { 160 | var filename = "%s%s".printf(appDataFolder,dbPreference); 161 | 162 | var file = File.new_for_path(filename); 163 | var exists = file.query_exists(); 164 | 165 | //文件不存在->创建文件 166 | if(!exists) 167 | { 168 | createAppFolder(); 169 | file.create(FileCreateFlags.NONE); 170 | } 171 | var jsonStr = JsonUtil.jsonStr(node); 172 | var output = file.replace(null,false,FileCreateFlags.NONE); 173 | output.write(jsonStr.data); 174 | } 175 | 176 | /** 177 | * 178 | * 根据UUID获取数据源 179 | * 180 | * 181 | **/ 182 | public static DataSource? getDataSource(string uuid) 183 | { 184 | var dataSource = map.get(uuid); 185 | 186 | if(dataSource == null) 187 | { 188 | warning("Cant' find any dataSource for [%s]".printf(uuid)); 189 | } 190 | 191 | return dataSource; 192 | } 193 | 194 | 195 | 196 | /** 197 | * 198 | * 创建配置根目录 199 | * 200 | */ 201 | private static void createAppFolder() throws FXError 202 | { 203 | var file = File.new_for_path(appDataFolder); 204 | 205 | if(file.query_exists()) 206 | { 207 | return; 208 | } 209 | 210 | try 211 | { 212 | //创建目录 213 | var success = file.make_directory(); 214 | debug("Folder:[%s] create result:%s",appDataFolder,success ? "True" : "False"); 215 | } 216 | catch(Error err) 217 | { 218 | warning("Config dir create fail:%s",err.message); 219 | throw new FXError.ERROR(err.message); 220 | } 221 | } 222 | 223 | private static DataSource create(Json.Object obj) 224 | { 225 | var dataSource = new DataSource(obj.get_int_member(Constant.TYPE)); 226 | 227 | dataSource.name = obj.get_string_member(Constant.NAME); 228 | dataSource.user = obj.get_string_member(Constant.USER); 229 | dataSource.host = obj.get_string_member(Constant.HOST); 230 | dataSource.uuid = obj.get_string_member(Constant.UUID); 231 | dataSource.port = (int)obj.get_int_member(Constant.PORT); 232 | dataSource.database = obj.get_string_member(Constant.DATABASE); 233 | dataSource.maxWait = (int)obj.get_int_member(Constant.MAX_WAIT); 234 | dataSource.maxSize = (int)obj.get_int_member(Constant.MAX_SIZE); 235 | dataSource.comment = obj.get_string_member(Constant.COMMENT); 236 | dataSource.password = obj.get_string_member(Constant.PASSWORD); 237 | dataSource.dbType = (DatabaseType)obj.get_int_member(Constant.TYPE); 238 | dataSource.dbFilePath = obj.get_string_member(Constant.DB_FILE_PATH); 239 | dataSource.saveModel = (SaveModel)obj.get_int_member(Constant.SAVE_MODEL); 240 | dataSource.authModel = (AuthModel)obj.get_int_member(Constant.AUTH_MODEL); 241 | 242 | return dataSource; 243 | } 244 | 245 | private static Json.Object toJson(DataSource dataSource) 246 | { 247 | 248 | var builder = new Json.Builder(); 249 | 250 | builder.begin_object(); 251 | 252 | builder.set_member_name(Constant.UUID); 253 | builder.add_string_value(dataSource.uuid); 254 | 255 | builder.set_member_name(Constant.TYPE); 256 | builder.add_int_value(dataSource.dbType); 257 | 258 | builder.set_member_name(Constant.NAME); 259 | builder.add_string_value(dataSource.name); 260 | 261 | builder.set_member_name(Constant.COMMENT); 262 | builder.add_string_value(dataSource.comment); 263 | 264 | builder.set_member_name(Constant.DATABASE); 265 | builder.add_string_value(dataSource.database); 266 | 267 | builder.set_member_name(Constant.AUTH_MODEL); 268 | builder.add_int_value(dataSource.authModel); 269 | 270 | builder.set_member_name(Constant.HOST); 271 | builder.add_string_value(dataSource.host); 272 | 273 | builder.set_member_name(Constant.PORT); 274 | builder.add_int_value(dataSource.port); 275 | 276 | builder.set_member_name(Constant.SAVE_MODEL); 277 | builder.add_int_value(dataSource.saveModel); 278 | 279 | builder.set_member_name(Constant.USER); 280 | builder.add_string_value(dataSource.user); 281 | 282 | builder.set_member_name(Constant.PASSWORD); 283 | builder.add_string_value(dataSource.password); 284 | 285 | builder.set_member_name(Constant.DB_FILE_PATH); 286 | builder.add_string_value(dataSource.dbFilePath); 287 | 288 | builder.set_member_name(Constant.MAX_SIZE); 289 | builder.add_int_value(dataSource.maxSize); 290 | 291 | builder.set_member_name(Constant.MAX_WAIT); 292 | builder.add_int_value(dataSource.maxWait); 293 | 294 | builder.end_object(); 295 | 296 | var node = builder.get_root(); 297 | return node.get_object(); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/config/nav-tree.vala: -------------------------------------------------------------------------------- 1 | public enum NTRow 2 | { 3 | /** 4 | * 5 | * 数据库 6 | * 7 | **/ 8 | ROOT, 9 | /** 10 | * 11 | * 12 | * schema 13 | * 14 | */ 15 | SCHEMA, 16 | /** 17 | * 18 | * 表文件夹 19 | * 20 | **/ 21 | TABLE_FOLDER, 22 | /** 23 | * 24 | * 视图文件夹 25 | * 26 | **/ 27 | VIEW_FOLDER, 28 | /** 29 | * 30 | * 表 31 | * 32 | **/ 33 | TABLE, 34 | /** 35 | * 36 | * 37 | * 视图 38 | * 39 | * 40 | **/ 41 | VIEW 42 | } 43 | 44 | /** 45 | * 46 | * 47 | * 枚举导航树列 48 | * 49 | **/ 50 | public enum NavTreeCol 51 | { 52 | /** 53 | * 54 | * 图标列 55 | * 56 | **/ 57 | ICON, 58 | /** 59 | * 60 | * 61 | * 名称列 62 | * 63 | **/ 64 | NAME, 65 | /** 66 | * 67 | * 68 | * 列类别字段 69 | * 70 | **/ 71 | NT_ROW, 72 | /** 73 | * 74 | * 当前行激活状态 75 | * 76 | **/ 77 | STATUS, 78 | /** 79 | * 80 | * 数据源标识 81 | * 82 | **/ 83 | UUID, 84 | /** 85 | * 86 | * 列数 87 | * 88 | **/ 89 | COL_NUM; 90 | } 91 | /** 92 | * 93 | *枚举导航数菜单 94 | * 95 | **/ 96 | public enum NavTreeItem 97 | { 98 | OPEN, 99 | BREAK_OFF, 100 | EDIT, 101 | DELETE, 102 | FLUSH, 103 | COMMAND_LINE, 104 | NEW_QUERY, 105 | DDL, 106 | CLEAR_TABLE, 107 | COPY, 108 | RENAME 109 | } 110 | 111 | public enum NavTRowStatus 112 | { 113 | //非激活状态 114 | INACTIVE, 115 | //激活中 116 | ACTIVING, 117 | //已激活 118 | ACTIVED, 119 | //所有状态 120 | ANY 121 | } 122 | -------------------------------------------------------------------------------- /src/controller/main-controller.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | [GtkTemplate (ui = "/cn/navclub/ldbfx/ui/main-window.xml")] 4 | public class MainController : Gtk.ApplicationWindow { 5 | [GtkChild] 6 | private unowned Paned paned; 7 | [GtkChild] 8 | private unowned Stack stack; 9 | [GtkChild] 10 | public unowned Notebook notebook; 11 | [GtkChild] 12 | private unowned TreeView navTree; 13 | [GtkChild] 14 | private unowned TreeViewColumn iconCol; 15 | [GtkChild] 16 | private unowned TreeViewColumn nameCol; 17 | 18 | private Gtk.TreeStore treeModel; 19 | 20 | //保存引用,防止对象被回收 21 | private NavTreeEvent navTreeEvent; 22 | 23 | 24 | public MainController (Gtk.Application app) { 25 | Object (application: app); 26 | 27 | this.initMainUI(); 28 | } 29 | 30 | public void initMainUI() 31 | { 32 | this.show_menubar = true; 33 | this.paned.shrink_start_child = false; 34 | 35 | this.treeModel = new Gtk.TreeStore 36 | ( 37 | NavTreeCol.COL_NUM, 38 | Type.STRING, 39 | Type.STRING, 40 | Type.INT, 41 | Type.INT, 42 | Type.STRING 43 | ); 44 | 45 | this.nameCol.set_expand(true); 46 | 47 | var dataSources = AppConfig.fetchDataSource(); 48 | foreach(var dataSource in dataSources){ 49 | this.createNavRoot(dataSource); 50 | } 51 | 52 | this.navTree.model = this.treeModel; 53 | //注册自定义弹出菜单 54 | this.navTreeEvent = new NavTreeEvent.register(this.navTree,this); 55 | } 56 | 57 | [GtkCallback] 58 | public void noteChildChange(Gtk.Widget child,uint page) 59 | { 60 | var size = this.notebook.get_n_pages(); 61 | this.stack.set_visible_child_name(size > 0 ? "page1" : "page0"); 62 | } 63 | 64 | public void auDataSource(DataSource dataSource,bool update) 65 | { 66 | if(!update) 67 | { 68 | this.createNavRoot(dataSource); 69 | } 70 | } 71 | 72 | private void createNavRoot(DataSource dataSource) 73 | { 74 | var feature = DatabaseFeature.getFeature(dataSource.dbType); 75 | 76 | var iter = new TreeIter(); 77 | this.treeModel.append(out iter,null); 78 | 79 | 80 | this.treeModel.set 81 | ( 82 | iter , 83 | NavTreeCol.ICON , feature.icon, 84 | NavTreeCol.NAME , dataSource.name, 85 | NavTreeCol.NT_ROW , NTRow.ROOT , 86 | NavTreeCol.STATUS , NavTRowStatus.INACTIVE, 87 | NavTreeCol.UUID , dataSource.uuid 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/dialog/connect-dialog.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | 4 | private class FeatureListItem : Box 5 | { 6 | private const string DEFAULT_CLASS_NAME = "feature-list-item"; 7 | 8 | public unowned DatabaseFeature feature{ 9 | private set; 10 | get; 11 | } 12 | 13 | public FeatureListItem(unowned DatabaseFeature feature) 14 | { 15 | 16 | this.vexpand = false; 17 | this.feature = feature; 18 | this.valign = Align.CENTER; 19 | this.orientation = Orientation.VERTICAL; 20 | 21 | var label = new Label(feature.name); 22 | var image = new Image.from_icon_name(feature.icon); 23 | 24 | image.pixel_size = 100; 25 | 26 | this.append(image); 27 | this.append(label); 28 | 29 | this.add_css_class(DEFAULT_CLASS_NAME); 30 | } 31 | } 32 | 33 | [GtkTemplate ( ui = "/cn/navclub/ldbfx/ui/connect-dialog.xml" )] 34 | public class ConnectDialog : Gtk.Dialog { 35 | 36 | 37 | public signal void callback(DataSource dataSource); 38 | 39 | [GtkChild] 40 | private unowned Entry name; 41 | [GtkChild] 42 | private unowned Entry comment; 43 | [GtkChild] 44 | private unowned Button cancel; 45 | [GtkChild] 46 | private unowned Stack stack; 47 | [GtkChild] 48 | private unowned Label tText; 49 | [GtkChild] 50 | private unowned Button apply; 51 | [GtkChild] 52 | private unowned Button stepBtn; 53 | [GtkChild] 54 | private unowned Box compactBox; 55 | [GtkChild] 56 | private unowned Spinner spinner; 57 | [GtkChild] 58 | private unowned FlowBox flowBox; 59 | 60 | 61 | private string uuid; 62 | 63 | private DataSourceConpact compact; 64 | 65 | //当前数据库配置信息 66 | private unowned DatabaseFeature feature; 67 | 68 | public ConnectDialog() 69 | { 70 | this.compact = null; 71 | this.visible = true; 72 | this.createListItem(); 73 | } 74 | 75 | public ConnectDialog.fromEdit(string uuid) 76 | { 77 | var dataSource = AppConfig.getDataSource(this.uuid=uuid); 78 | 79 | this.feature = DatabaseFeature.getFeature(dataSource.dbType); 80 | 81 | this.nextOrSave(); 82 | 83 | this.visible = true; 84 | this.stepBtn.visible = false; 85 | 86 | compact.initCompact(dataSource); 87 | 88 | this.name.text = dataSource.name; 89 | this.comment.text = dataSource.comment; 90 | } 91 | 92 | 93 | /** 94 | * 95 | * 96 | * 创建支持数据库条目 97 | * 98 | */ 99 | private void createListItem() 100 | { 101 | var list = DatabaseFeature.getFeatures(); 102 | 103 | foreach(var feature in list) 104 | { 105 | var box = new FlowBoxChild(); 106 | box.width_request = 100; 107 | box.height_request = 100; 108 | box.child = new FeatureListItem(feature); 109 | this.flowBox.append(box); 110 | } 111 | 112 | //默认选中第一个 113 | if((this.apply.sensitive =(list.length > 0))) 114 | { 115 | this.feature = list[0]; 116 | } 117 | 118 | } 119 | 120 | [GtkCallback] 121 | public void nextOrSave() 122 | { 123 | var visibleName = this.stack.visible_child_name; 124 | if(visibleName == "page0") 125 | { 126 | this.apply.label = _("_Ok"); 127 | this.stepBtn.visible = true; 128 | if(this.compact != null) 129 | { 130 | this.name.text = ""; 131 | this.comment.text = ""; 132 | this.compactBox.remove(this.compact.content()); 133 | } 134 | if(this.feature.dbType == DatabaseType.SQLITE) 135 | { 136 | this.compact = new SqliteCompact(this); 137 | } 138 | else 139 | { 140 | this.compact = new CommonDataSourceCompact(); 141 | } 142 | this.compactBox.append(this.compact.content()); 143 | this.stack.set_visible_child_name("page1"); 144 | } 145 | else 146 | { 147 | //执行保存 148 | this.save(); 149 | } 150 | } 151 | 152 | [GtkCallback] 153 | public void back() 154 | { 155 | this.stack.set_visible_child_name("page0"); 156 | this.apply.label = _("_Next"); 157 | this.stepBtn.visible = false; 158 | } 159 | 160 | [GtkCallback] 161 | public void flowBoxChildActive(FlowBoxChild box){ 162 | this.apply.sensitive = true; 163 | var item = box.child as FeatureListItem; 164 | this.feature = item.feature; 165 | } 166 | 167 | /** 168 | * 169 | * 170 | * 171 | * 检查当前配置是否可用 172 | * 173 | * 174 | */ 175 | [GtkCallback] 176 | public async void testConnect(Gtk.Button btn) 177 | { 178 | var dataSource = new DataSource(this.feature.dbType); 179 | var success = this.compact.toDataSource(dataSource); 180 | if(!success) 181 | { 182 | return; 183 | } 184 | this.spinner.start(); 185 | 186 | FXError error = null; 187 | 188 | DatabaseInfo info = null; 189 | 190 | SourceFunc callback = testConnect.callback; 191 | 192 | WorkDetail work = ()=>{ 193 | try 194 | { 195 | SqlConnection con; 196 | if(this.feature.dbType == DatabaseType.SQLITE) 197 | { 198 | con = new SqliteConnection.without(dataSource); 199 | } 200 | else 201 | { 202 | con = new MysqlConnection.without(dataSource); 203 | } 204 | con.connect(); 205 | info = con.serverInfo(); 206 | } 207 | catch(FXError e) 208 | { 209 | error = e; 210 | } 211 | Idle.add(callback); 212 | }; 213 | 214 | AsyncWork.create(work).execute(); 215 | 216 | yield; 217 | 218 | if(error != null) 219 | { 220 | this.tText.label = error.message; 221 | } 222 | else 223 | { 224 | this.tText.label = "%s(%s)".printf(info.name,info.version); 225 | } 226 | 227 | this.spinner.stop(); 228 | } 229 | 230 | [GtkCallback] 231 | public void dialogClose() 232 | { 233 | this.close(); 234 | } 235 | 236 | private async void save() 237 | { 238 | 239 | var dataSource = DataSource.default(this.feature.dbType); 240 | var success = this.compact.toDataSource(dataSource); 241 | if(!success || !basicValid(dataSource)) 242 | { 243 | return; 244 | } 245 | 246 | var update = false; 247 | 248 | if((update = (uuid != null))) 249 | { 250 | dataSource.uuid = this.uuid; 251 | } 252 | 253 | dataSource.maxWait = 3; 254 | dataSource.maxSize = 10; 255 | 256 | FXError error = null; 257 | SourceFunc callback = save.callback; 258 | var work = AsyncWork.create(()=>{ 259 | try 260 | { 261 | AppConfig.addDataSource(dataSource,update); 262 | } 263 | catch(FXError e) 264 | { 265 | error = e; 266 | var errmsg = error.message; 267 | warning(@"Write config file fail:$errmsg"); 268 | } 269 | Idle.add(callback); 270 | }); 271 | 272 | work.execute(); 273 | 274 | yield; 275 | 276 | if(error != null) 277 | { 278 | UIUtil.textNotification(_("Save config fail")); 279 | return; 280 | } 281 | 282 | this.callback(dataSource); 283 | 284 | this.close(); 285 | } 286 | 287 | private bool basicValid(DataSource? dataSource) 288 | { 289 | var name = this.name.text.strip(); 290 | var b = UIUtil.formValid(this.name,()=>name!=""); 291 | if(b && dataSource != null) 292 | { 293 | dataSource.name= name; 294 | dataSource.comment = this.comment.text; 295 | } 296 | return b; 297 | } 298 | 299 | } 300 | 301 | public interface DataSourceConpact:Object 302 | { 303 | /** 304 | * 305 | * 表单验证 306 | * 307 | **/ 308 | public abstract bool formValid(); 309 | 310 | /** 311 | * 312 | * 初始化组建 313 | * 314 | */ 315 | public abstract void initCompact(DataSource dataSource); 316 | 317 | /** 318 | * 319 | * 将当前表单内容转换为 DataSource 模型 320 | * 321 | **/ 322 | public abstract bool toDataSource(DataSource dataSource); 323 | 324 | /** 325 | * 326 | * 获取当前ui组件 327 | * 328 | */ 329 | public abstract Widget content(); 330 | } -------------------------------------------------------------------------------- /src/dialog/design-table.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | /** 3 | * 4 | * Design table dialog 5 | * 6 | */ 7 | [GtkTemplate (ui="/cn/navclub/ldbfx/ui/design-table.xml")] 8 | public class DDTable : Dialog 9 | { 10 | public DDTable() 11 | { 12 | this.visible = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/main.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | using Gee; 3 | 4 | public const string EXIT_ACTION_NAME = "exit"; 5 | public const string NEW_CONNECT_ACTION_NAME = "new"; 6 | public const string FULL_SCREEN_ACTION_NAME = "fullscreen"; 7 | 8 | 9 | public class Application : Gtk.Application 10 | { 11 | private Object mutex; 12 | 13 | public static Application ctx; 14 | 15 | public MainController controller{ 16 | private set; 17 | get; 18 | } 19 | 20 | private Map pools; 21 | 22 | private const GLib.ActionEntry[] actionEntries = 23 | { 24 | { NEW_CONNECT_ACTION_NAME, newConnect }, 25 | { EXIT_ACTION_NAME, exit }, 26 | { FULL_SCREEN_ACTION_NAME, fullscreen } 27 | }; 28 | 29 | public Application(){ 30 | Object(application_id:APPLICATION_ID,flags:ApplicationFlags.FLAGS_NONE); 31 | 32 | this.mutex = new Object(); 33 | this.activate.connect(this.appInit); 34 | this.pools = new HashMap(); 35 | } 36 | 37 | public void addTab(TabService service,bool selected=true) 38 | { 39 | var index = -1; 40 | if( (index = this.tabExist(service.scheme(),service.path(),selected)) != -1 ) 41 | { 42 | return; 43 | } 44 | var label = service.tab(); 45 | var content = service as Gtk.Widget; 46 | var notebook = this.controller.notebook; 47 | index = notebook.append_page(content,label); 48 | if( selected ) 49 | { 50 | notebook.page = index; 51 | } 52 | } 53 | 54 | /* 55 | * 56 | * 57 | * Remove fix prefix tab from Notebook 58 | * 59 | * 60 | **/ 61 | public void removeTab(string prefix) 62 | { 63 | var tabs = new Widget[0]; 64 | var notebook = this.controller.notebook; 65 | var num = notebook.get_n_pages(); 66 | for(var i = 0;i < num ; i++) 67 | { 68 | var service = notebook.get_nth_page(i) as TabService; 69 | if(service.path().has_prefix(prefix)) 70 | { 71 | tabs+=service.content(); 72 | } 73 | } 74 | foreach(var tab in tabs) 75 | { 76 | notebook.detach_tab(tab); 77 | } 78 | } 79 | 80 | public int tabExist(TabScheme scheme,string path,bool selected=true) 81 | { 82 | var index = -1; 83 | var notebook = this.controller.notebook; 84 | var num = notebook.get_n_pages(); 85 | var temp = TabScheme.toFullPath(scheme,path); 86 | 87 | for (int i = 0; i < num; i++) 88 | { 89 | var service = notebook.get_nth_page(i) as TabService; 90 | 91 | if( service.path() == temp) 92 | { 93 | index = i; 94 | break; 95 | } 96 | } 97 | 98 | if( index != -1 && selected ) 99 | { 100 | notebook.page = index; 101 | } 102 | 103 | return index; 104 | } 105 | 106 | /** 107 | * 108 | * 109 | * 创建数据库连接池 110 | * 111 | **/ 112 | public SqlConnectionPool getConnPool(string uuid) throws FXError 113 | { 114 | lock(mutex) 115 | { 116 | var pool = this.pools.get(uuid); 117 | if(pool != null) 118 | { 119 | return pool; 120 | } 121 | 122 | var dataSource = AppConfig.getDataSource(uuid); 123 | pool = new SqlConnectionPool(dataSource); 124 | this.pools.set(uuid,pool); 125 | 126 | return pool; 127 | } 128 | } 129 | 130 | /** 131 | * 132 | * 移除某个连接池 133 | * 134 | **/ 135 | public void removePool(string uuid) 136 | { 137 | lock(mutex) 138 | { 139 | var pool = this.pools.get(uuid); 140 | if( pool == null ){ 141 | return; 142 | } 143 | //移除缓存 144 | this.pools.remove(uuid); 145 | //执行资源释放 146 | pool.shutdown(); 147 | } 148 | } 149 | 150 | /** 151 | * 152 | * 153 | * 便利方法用户获取连接 154 | * 155 | **/ 156 | public static SqlConnection getConnection(string uuid) throws FXError 157 | { 158 | if(ctx == null) 159 | { 160 | throw new FXError.ERROR("Application can't instance!"); 161 | } 162 | return ctx.getConnPool(uuid).getConnection(); 163 | } 164 | 165 | /** 166 | * 167 | * 168 | * 应用初始化 169 | * 170 | **/ 171 | public void appInit() 172 | { 173 | //设置窗口默认图标 174 | Gtk.Window.set_default_icon_name("cn.navclub.ldbfx"); 175 | 176 | //注册快捷方式 177 | set_accels_for_action("app."+ EXIT_ACTION_NAME, { "e" }); 178 | set_accels_for_action("app."+NEW_CONNECT_ACTION_NAME, { "n" }); 179 | set_accels_for_action("app."+FULL_SCREEN_ACTION_NAME, { "f"}); 180 | 181 | 182 | //注册action对应函数句柄 183 | add_action_entries(actionEntries, this); 184 | 185 | // 设置应用自定义图标搜索路径 186 | var iconTheme = UIUtil.getIconTheme(); 187 | 188 | iconTheme.add_resource_path(ICON_SEARCH_PATH); 189 | 190 | //加载全局应用样式 191 | var styleProvider = new Gtk.CssProvider(); 192 | styleProvider.load_from_resource("/cn/navclub/ldbfx/style/style.css"); 193 | Gtk.StyleContext.add_provider_for_display( 194 | Gdk.Display.get_default(), 195 | styleProvider, 196 | Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION 197 | ); 198 | 199 | Gtk.Settings.get_default().gtk_application_prefer_dark_theme = true; 200 | 201 | this.menubar = (Menu)UIUtil.loadXmlUI("menubar.xml","menubar"); 202 | 203 | if(this.controller == null){ 204 | this.controller = new MainController(this); 205 | } 206 | 207 | this.controller.present(); 208 | } 209 | 210 | /** 211 | * 212 | * 新建连接 213 | * 214 | **/ 215 | public void newConnect() 216 | { 217 | var dialog = new ConnectDialog(); 218 | dialog.callback.connect((dataSource)=>{ 219 | this.controller.auDataSource(dataSource,false); 220 | }); 221 | } 222 | 223 | /** 224 | * 225 | * 226 | * 全屏 227 | * 228 | * */ 229 | public void fullscreen(SimpleAction action,Variant? parameter) 230 | { 231 | this.controller.fullscreened = !this.controller.fullscreened; 232 | } 233 | 234 | /** 235 | * 236 | * 退出程序 237 | * 238 | **/ 239 | public void exit() 240 | { 241 | var alert = FXAlert.confirm(null,_("_Exit confirm")); 242 | alert.response.connect(apply=>{ 243 | if(!apply) 244 | { 245 | return; 246 | } 247 | Process.exit(0); 248 | }); 249 | } 250 | 251 | 252 | public static int main (string[] args) 253 | { 254 | try 255 | { 256 | 257 | // 258 | // 初始化应用目录 259 | // 260 | AppConfig.initAppDataFolder(); 261 | 262 | // 263 | // 国际化配置 264 | // 265 | Intl.setlocale(LocaleCategory.ALL,""); 266 | Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); 267 | Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "utf-8"); 268 | Intl.textdomain (GETTEXT_PACKAGE); 269 | 270 | /// 271 | /// Register non gtk-core class 272 | /// 273 | typeof(SQLSourceView).ensure(); 274 | typeof(GtkSource.View).ensure(); 275 | 276 | //初始化线程池 277 | AsyncWork.createThreadPool(20); 278 | } 279 | catch(Error e) 280 | { 281 | error("Application init failed:"+e.message); 282 | Process.exit(0); 283 | } 284 | 285 | ctx = new Application(); 286 | return ctx.run(args); 287 | } 288 | } 289 | 290 | 291 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | vala_compiler = meson.get_compiler('vala') 2 | 3 | dbfx_vala_sources = [ 4 | resources, 5 | 'main.vala', 6 | 'tab-service.vala', 7 | 'util/ui-util.vala', 8 | 'util/os-util.vala', 9 | 'util/json-util.vala', 10 | 'config/nav-tree.vala', 11 | 'config/app-config.vala', 12 | 'widget/fx-alert.vala', 13 | 'widget/notebook-ddl.vala', 14 | 'widget/sqlite-compact.vala', 15 | 'widget/sql-source-view.vala', 16 | 'widget/data-source-compact.vala', 17 | 'widget/notebook-tab.vala', 18 | 'widget/notebook-table.vala', 19 | 'model/table-row-meta.vala', 20 | 'event/nav-tree-event.vala', 21 | 'dialog/connect-dialog.vala', 22 | 'dialog/design-table.vala', 23 | 'controller/main-controller.vala' 24 | ] 25 | 26 | 27 | dbfx_vala_deps = [ 28 | gio, 29 | gtk, 30 | libgee, 31 | libxml, 32 | libjson, 33 | libdbfx_dep, 34 | libsourceview 35 | ] 36 | 37 | dbfx_c_args = [ 38 | '-include', 'config.h', 39 | ] 40 | 41 | 42 | executable( 43 | 'ldbfx', 44 | dbfx_vala_sources, 45 | install: true, 46 | c_args:dbfx_c_args, 47 | dependencies: dbfx_vala_deps, 48 | include_directories: conf_include_dir 49 | ) 50 | -------------------------------------------------------------------------------- /src/model/table-row-meta.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 表格行原始数据 4 | * 5 | **/ 6 | public class TableRowMeta : Object 7 | { 8 | /** 9 | * 10 | * 11 | * 记录当前渲染单元格位位置 12 | * 13 | **/ 14 | public int index{ 15 | private set; 16 | get; 17 | } 18 | 19 | public string[] value{ 20 | private set; 21 | private get; 22 | } 23 | 24 | 25 | /** 26 | * 27 | * 判断当前值是否为空 28 | * 29 | */ 30 | public bool isNull{ 31 | private set; 32 | get; 33 | } 34 | 35 | public TableRowMeta(string[] value) 36 | { 37 | this.value = value; 38 | this.index = 0; 39 | } 40 | 41 | public string getStrValue(){ 42 | if(this.index >= this.value.length) 43 | { 44 | index = 0; 45 | } 46 | var str = this.value[index++]; 47 | return (this.isNull = (str == Constant.NULL_SYMBOL)) ? "" : str; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tab-service.vala: -------------------------------------------------------------------------------- 1 | public enum TabScheme 2 | { 3 | TABLE, 4 | DDL; 5 | 6 | public static string toFullPath(TabScheme schema,string path) 7 | { 8 | string prefix; 9 | if(schema == TABLE) 10 | { 11 | prefix = "table"; 12 | } 13 | else if (schema == DDL) 14 | { 15 | prefix = "ddl"; 16 | } 17 | else 18 | { 19 | prefix = "app"; 20 | } 21 | return @"$prefix:$path"; 22 | } 23 | } 24 | 25 | public interface TabService : GLib.Object 26 | { 27 | /** 28 | * 29 | * 30 | * 获取Tab实例 31 | * 32 | **/ 33 | public abstract NotebookTab tab(); 34 | 35 | /** 36 | * 37 | * 38 | * 获取Notebook实例 39 | * 40 | **/ 41 | public unowned Gtk.Notebook notebook(){ 42 | return Application.ctx.controller.notebook; 43 | } 44 | 45 | 46 | /** 47 | * 48 | * 49 | * 获取Tab路径字符串 50 | * 51 | **/ 52 | public abstract string path(); 53 | 54 | 55 | /** 56 | * 57 | * 58 | * 当前Tab销毁时调用该函数 59 | * 60 | * 61 | **/ 62 | public virtual void destory() throws FXError 63 | { 64 | 65 | } 66 | 67 | 68 | /** 69 | * 70 | * 71 | * 获取指定位置值 72 | * 73 | * 74 | **/ 75 | protected virtual string getPosVal(string str,int pos) 76 | { 77 | var array = str.split(":"); 78 | var len = array.length; 79 | pos = pos < 0 ? len+pos : pos; 80 | return array[pos]; 81 | } 82 | 83 | 84 | /** 85 | * 86 | * 87 | * 获取当前视图组件 88 | * 89 | * 90 | */ 91 | public abstract unowned Gtk.Widget content(); 92 | 93 | 94 | 95 | /** 96 | * 97 | * 98 | * Tab scheme 99 | * 100 | * 101 | */ 102 | public abstract TabScheme scheme(); 103 | } -------------------------------------------------------------------------------- /src/util/json-util.vala: -------------------------------------------------------------------------------- 1 | public class JsonUtil:Object 2 | { 3 | /** 4 | * 5 | * 将json对象转换位json字符串 6 | * 7 | */ 8 | public static string jsonStr(Json.Node node) 9 | { 10 | var generator = new Json.Generator(); 11 | generator.set_root(node); 12 | return generator.to_data(null); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/util/os-util.vala: -------------------------------------------------------------------------------- 1 | using Gdk; 2 | 3 | public class OSUtil : Object 4 | { 5 | /* 6 | * 7 | * Put plain text input system clipboard 8 | * 9 | */ 10 | public static void setCPBText(string text) 11 | { 12 | var display = Display.get_default(); 13 | 14 | if(display == null) 15 | { 16 | return; 17 | } 18 | 19 | var clipboard = display.get_clipboard(); 20 | 21 | clipboard.set_text(text); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/util/sql-util.vala: -------------------------------------------------------------------------------- 1 | 2 | 3 | public delegate void SQLExecutorFinally(FXError error); 4 | 5 | public delegate void SQLExecutorContext(SqlConnection connection); 6 | 7 | public class SQLUtil 8 | { 9 | public static async void asyncTask(string uuid,SQLExecutorContext ctx,SQLExecutorFinally? finally){ 10 | var work = AsyncWork.create(()=>{ 11 | FXError error = null; 12 | SqlConnection con = null; 13 | try 14 | { 15 | con = Application.ctx.getConnection(uuid); 16 | ctx(con); 17 | } 18 | catch(FXError e) 19 | { 20 | error = e; 21 | } 22 | finally 23 | { 24 | if(con != null) 25 | { 26 | con.close(); 27 | } 28 | if(finally != null) 29 | { 30 | finally(error); 31 | } 32 | } 33 | }); 34 | 35 | yield work.execute(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/util/ui-util.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | /** 4 | * 5 | * 表单验证失败显示样式 6 | * 7 | */ 8 | private const string FORM_VALID_FAIL = "form-valid-fail"; 9 | 10 | /** 11 | * 12 | * 表单验证 13 | * 14 | */ 15 | public delegate bool FormValidator(); 16 | 17 | public class UIUtil : Object { 18 | 19 | private static Gtk.IconTheme iconTheme = null; 20 | 21 | /** 22 | * 23 | * 加载UI视图 24 | * 25 | **/ 26 | public static Object? loadXmlUI(string ui,string id) 27 | { 28 | return new Gtk.Builder.from_resource (@"/cn/navclub/ldbfx/ui/$ui").get_object(id); 29 | } 30 | 31 | /** 32 | * 33 | * 34 | * 获取主题实例 35 | * 36 | **/ 37 | public static unowned Gtk.IconTheme getIconTheme() 38 | { 39 | if(iconTheme == null) 40 | { 41 | iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()); 42 | } 43 | return iconTheme; 44 | } 45 | /** 46 | * 47 | * 显示消息对话框 48 | * 49 | **/ 50 | public static void showMsgDialog(string msg,MessageType type,Gtk.Window ?window=null){ 51 | 52 | var dialog = new MessageDialog 53 | ( 54 | window, 55 | DialogFlags.DESTROY_WITH_PARENT, 56 | type, 57 | ButtonsType.OK, 58 | msg 59 | ); 60 | 61 | dialog.visible = true; 62 | } 63 | 64 | /** 65 | * 66 | * 表单验证 67 | * 68 | **/ 69 | public static bool formValid(Widget widget,FormValidator validator){ 70 | var success = validator(); 71 | var ctx = widget.get_style_context(); 72 | if(success) 73 | { 74 | if(ctx.has_class(FORM_VALID_FAIL)){ 75 | ctx.remove_class(FORM_VALID_FAIL); 76 | } 77 | 78 | }else 79 | { 80 | ctx.add_class(FORM_VALID_FAIL); 81 | } 82 | return success; 83 | } 84 | 85 | /** 86 | * 87 | * 普通文本通知 88 | * 89 | **/ 90 | public static void textNotification(string text) 91 | { 92 | var notification = new Notification(text); 93 | Application.ctx.send_notification(Uuid.string_random(),notification); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/widget/data-source-compact.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | [GtkTemplate(ui="/cn/navclub/ldbfx/ui/data-source-compact.xml")] 4 | public class CommonDataSourceCompact :Box,DataSourceConpact 5 | { 6 | [GtkChild] 7 | private unowned Entry user; 8 | [GtkChild] 9 | private unowned PasswordEntry password; 10 | [GtkChild] 11 | private unowned ComboBox authBox; 12 | [GtkChild] 13 | private unowned ComboBox saveBox; 14 | [GtkChild] 15 | private unowned Entry host; 16 | [GtkChild] 17 | private unowned Entry port; 18 | [GtkChild] 19 | private unowned Entry database; 20 | 21 | public CommonDataSourceCompact() 22 | { 23 | this.saveBox.active = 0; 24 | this.authBox.active = 0; 25 | } 26 | /** 27 | * 28 | * 表单验证 29 | * 30 | **/ 31 | public override bool formValid() 32 | { 33 | 34 | var host = this.host.text.strip(); 35 | var port = this.port.text.strip(); 36 | var user = this.user.text.strip(); 37 | 38 | var require = (authRequire()==AuthModel.USER_PASSWORD); 39 | 40 | var password = this.password.text.strip(); 41 | 42 | 43 | var a = UIUtil.formValid(this.host,()=>host!=""); 44 | var c = UIUtil.formValid(this.port,()=>port!=""); 45 | var d = UIUtil.formValid(this.user,()=>(!require || user!="")); 46 | var e = UIUtil.formValid(this.password,()=>(!require ||password!="")); 47 | 48 | return a && c && d && e; 49 | } 50 | 51 | /** 52 | * 53 | * 将当前表单内容转换为 DataSource 模型 54 | * 55 | **/ 56 | public override bool toDataSource(DataSource dataSource) 57 | { 58 | var valid = this.formValid(); 59 | 60 | if(valid){ 61 | dataSource.host = this.host.text; 62 | dataSource.database = this.database.text; 63 | dataSource.authModel = this.authBox.active; 64 | dataSource.saveModel = this.saveBox.active; 65 | dataSource.port = int.parse(this.port.text); 66 | 67 | if(this.authRequire() == AuthModel.USER_PASSWORD) 68 | { 69 | dataSource.user = this.user.text; 70 | dataSource.password = this.password.text; 71 | } 72 | else 73 | { 74 | dataSource.user = ""; 75 | dataSource.password = ""; 76 | } 77 | } 78 | 79 | return valid; 80 | } 81 | 82 | /** 83 | * 84 | * 获取当前ui组件 85 | * 86 | */ 87 | public override Widget content() 88 | { 89 | return this; 90 | } 91 | 92 | private AuthModel authRequire() 93 | { 94 | return (AuthModel)this.authBox.get_active(); 95 | } 96 | 97 | /** 98 | * 99 | * 认证方式改变 100 | * 101 | **/ 102 | [GtkCallback] 103 | public void authChange(){ 104 | 105 | var index = this.authBox.get_active(); 106 | 107 | //如果认证方式为`NONE`则禁用认证模块 108 | var disable = (index == AuthModel.USER_PASSWORD); 109 | 110 | this.user.sensitive = disable; 111 | this.saveBox.sensitive = disable; 112 | this.password.sensitive = disable; 113 | } 114 | 115 | public override void initCompact(DataSource dataSource) 116 | { 117 | this.user.text = dataSource.user; 118 | this.host.text = dataSource.host; 119 | this.database.text = dataSource.database; 120 | this.password.text = dataSource.password; 121 | this.authBox.active = dataSource.authModel; 122 | this.port.text = dataSource.port.to_string(); 123 | } 124 | } -------------------------------------------------------------------------------- /src/widget/fx-alert.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | public enum AlertType 4 | { 5 | /** 6 | * 7 | * 普通信息 8 | * 9 | */ 10 | INFOMATION, 11 | /** 12 | * 13 | * 警告信息 14 | * 15 | */ 16 | WARNING, 17 | /** 18 | * 19 | * 错误信息 20 | * 21 | */ 22 | ERROR, 23 | /** 24 | * 25 | * 确认框 26 | * 27 | */ 28 | CONFIRMATION 29 | } 30 | /** 31 | * 32 | * 一个包含信息、警告、错误对话框 33 | * 34 | */ 35 | [GtkTemplate (ui="/cn/navclub/ldbfx/ui/fx-alert.xml")] 36 | public class FXAlert : Window 37 | { 38 | [GtkChild] 39 | private unowned Button ok; 40 | [GtkChild] 41 | private unowned Image icon; 42 | [GtkChild] 43 | private unowned Button cancel; 44 | [GtkChild] 45 | private unowned Label subTitle; 46 | [GtkChild] 47 | private unowned Label textView; 48 | 49 | private AlertType type; 50 | 51 | public signal void response(bool ok); 52 | 53 | private FXAlert(AlertType type,string subTitle,string content) 54 | { 55 | this.type = type; 56 | string iconName; 57 | if(this.type == AlertType.INFOMATION) 58 | { 59 | iconName = "dbfx-alert-info"; 60 | this.title = _("_Information"); 61 | } 62 | else if(type == AlertType.WARNING) 63 | { 64 | iconName = "dbfx-alert-warn"; 65 | this.title = _("_Warn"); 66 | } 67 | else if(type == AlertType.CONFIRMATION) 68 | { 69 | iconName = "dbfx-alert-help"; 70 | this.title = _("_Confirmation"); 71 | } 72 | else 73 | { 74 | this.title = _("_Error"); 75 | iconName = "dbfx-alert-error"; 76 | } 77 | 78 | this.textView.label = content; 79 | this.subTitle.label = subTitle; 80 | 81 | this.icon.icon_name = iconName; 82 | 83 | this.ok.clicked.connect(()=>this.manualClose(true)); 84 | this.cancel.clicked.connect(()=>this.manualClose(false)); 85 | 86 | this.cancel.visible = (type == AlertType.CONFIRMATION); 87 | 88 | this.visible = true; 89 | 90 | } 91 | 92 | private void manualClose(bool ok) 93 | { 94 | this.response(ok); 95 | this.close(); 96 | } 97 | 98 | public static FXAlert info(string? subTitle,string content) 99 | { 100 | return new FXAlert(AlertType.INFOMATION,subTitle??_("_Information"),content); 101 | } 102 | 103 | public static FXAlert warn(string? subTitle,string content) 104 | { 105 | return new FXAlert(AlertType.WARNING,subTitle??_("_Warn info"),content); 106 | } 107 | 108 | public static FXAlert confirm(string? subTitle,string content) 109 | { 110 | return new FXAlert(AlertType.CONFIRMATION,subTitle??_("_Confirm info"),content); 111 | } 112 | 113 | public static FXAlert error(string? subTitle,string content) 114 | { 115 | return new FXAlert(AlertType.ERROR,subTitle??_("Error infor"),content); 116 | } 117 | } -------------------------------------------------------------------------------- /src/widget/notebook-ddl.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | using GtkSource; 3 | 4 | [GtkTemplate (ui = "/cn/navclub/ldbfx/ui/notebook-ddl.xml")] 5 | public class DDNotebook : Box, TabService 6 | { 7 | private bool view; 8 | 9 | private string pathStr; 10 | 11 | private unowned Buffer buffer; 12 | 13 | [GtkChild] 14 | private unowned SQLSourceView sourceView; 15 | 16 | public DDNotebook(string pathStr,bool view) 17 | { 18 | this.pathStr = pathStr; 19 | this.buffer = this.sourceView.buffer as Buffer; 20 | 21 | this.loadDDLScript(); 22 | } 23 | 24 | [GtkCallback] 25 | private async void loadDDLScript() 26 | { 27 | 28 | var uuid = this.getPosVal(this.pathStr,0); 29 | var table = this.getPosVal(this.pathStr,-1); 30 | var schema = this.getPosVal(this.pathStr,-3); 31 | 32 | string ddl = null; 33 | FXError error = null; 34 | 35 | var worker = AsyncWork.create(()=>{ 36 | SqlConnection con = null; 37 | try 38 | { 39 | con = Application.ctx.getConnection(uuid); 40 | ddl = con.ddl(schema,table,this.view); 41 | } 42 | catch(FXError e) 43 | { 44 | error = e; 45 | } 46 | finally 47 | { 48 | if(con != null) 49 | { 50 | con.close(); 51 | } 52 | Idle.add(loadDDLScript.callback); 53 | } 54 | }); 55 | 56 | worker.execute(); 57 | 58 | yield; 59 | 60 | if(error != null) 61 | { 62 | FXAlert.error(null,error.message); 63 | } 64 | else 65 | { 66 | this.buffer.text = ddl; 67 | } 68 | } 69 | 70 | [GtkCallback] 71 | public void copyText() 72 | { 73 | var text = this.buffer.text; 74 | if(StrUtil.isBlack(text)) 75 | { 76 | return; 77 | } 78 | //Copy ddl statement to clipboard 79 | OSUtil.setCPBText(text); 80 | UIUtil.textNotification(_("_Copy success")); 81 | } 82 | 83 | public NotebookTab tab() 84 | { 85 | var title = "[DDL]%s".printf(this.getPosVal(this.pathStr,-1)); 86 | return new NotebookTab("dbfx-ddl" , title, this , true); 87 | } 88 | 89 | public unowned Widget content() 90 | { 91 | return this; 92 | } 93 | 94 | 95 | public string path() 96 | { 97 | return TabScheme.toFullPath(TabScheme.DDL,this.pathStr); 98 | } 99 | 100 | public TabScheme scheme() 101 | { 102 | return TabScheme.DDL; 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /src/widget/notebook-tab.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | /** 3 | * 4 | * 5 | * 自定义Tab组件 6 | * 7 | * 8 | **/ 9 | public class NotebookTab : Box 10 | { 11 | private static string className; 12 | 13 | 14 | static construct 15 | { 16 | className = "tab"; 17 | } 18 | 19 | private Button btn; 20 | private weak TabService tabService; 21 | 22 | //判断当前是否处于加载状态 23 | public bool loading{ 24 | private set; 25 | get; 26 | } 27 | 28 | //加载状态信号 29 | public signal void loadStatus(bool loading); 30 | 31 | public NotebookTab(string iconName,string title,TabService tabService,bool closeable) 32 | { 33 | Object(orientation:Orientation.HORIZONTAL,spacing:5); 34 | 35 | this.tabService = tabService; 36 | 37 | var label = new Label(title); 38 | var spinner = new Spinner(); 39 | var icon = new Image.from_icon_name(iconName); 40 | btn = new Button.from_icon_name("dbfx-close"); 41 | 42 | spinner.visible = false; 43 | 44 | var notebook = this.tabService.notebook(); 45 | 46 | //添加关闭事件 47 | btn.clicked.connect(()=>{ 48 | notebook.detach_tab(this.tabService.content()); 49 | }); 50 | 51 | this.append(spinner); 52 | this.append(icon); 53 | this.append(label); 54 | this.append(btn); 55 | 56 | this.get_style_context().add_class(className); 57 | 58 | this.setCloseable(closeable); 59 | 60 | //动态显示图标和加载状态 61 | this.loadStatus.connect((loading)=>{ 62 | this.loading = loading; 63 | spinner.visible = !(icon.visible = !loading); 64 | if(spinner.visible) 65 | { 66 | spinner.start(); 67 | } 68 | else 69 | { 70 | spinner.stop(); 71 | } 72 | }); 73 | } 74 | 75 | public void setCloseable(bool closeable) 76 | { 77 | this.btn.visible = closeable; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/widget/notebook-table.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | using Gee; 3 | 4 | /* 5 | * 6 | * 7 | * NoteBook Table 8 | * 9 | * 10 | **/ 11 | [GtkTemplate (ui="/cn/navclub/ldbfx/ui/notebook-table.xml")] 12 | public class NotebookTable : Box, TabService 13 | { 14 | private bool view; 15 | private string uuid; 16 | private int64 total; 17 | private string pathStr; 18 | private PageQuery pageQuery; 19 | private NotebookTab notebookTab; 20 | private MultiSelection selection; 21 | private GLib.ListStore listStore; 22 | private SignalListItemFactory factory; 23 | 24 | private Gee.List list = null; 25 | //缓存当前列属性 26 | private Gee.List columns; 27 | 28 | 29 | [GtkChild] 30 | private unowned Entry sEntry; 31 | [GtkChild] 32 | private unowned Entry wEntry; 33 | [GtkChild] 34 | private unowned Label rowNLum; 35 | [GtkChild] 36 | private unowned ColumnView tableView; 37 | 38 | 39 | public NotebookTable(string path,bool view) 40 | { 41 | this.total = 0; 42 | this.view = view; 43 | this.pathStr = path; 44 | 45 | this.uuid = this.getPosVal(path,0); 46 | 47 | this.list = new ArrayList(); 48 | this.factory = new SignalListItemFactory(); 49 | this.columns = new ArrayList(); 50 | 51 | this.listStore = new GLib.ListStore(typeof(TableRowMeta)); 52 | this.selection = new MultiSelection(this.listStore); 53 | 54 | this.pageQuery = new PageQuery(this.getPosVal(this.pathStr,-3),this.getPosVal(this.pathStr,-1)); 55 | 56 | this.notebookTab = new NotebookTab( view ? "dbfx-view" : "dbfx-table" , getPosVal(pathStr,-1) , this, true ); 57 | 58 | var count = 0; 59 | this.factory.bind.connect(listItem=>{ 60 | var item = listItem.item as TableRowMeta; 61 | var label = listItem.child as Label; 62 | 63 | var text = item.getStrValue(); 64 | 65 | if(item.isNull) 66 | { 67 | label.add_css_class("table-cell-high-light"); 68 | } 69 | else 70 | { 71 | label.remove_css_class("table-cell-high-light"); 72 | } 73 | 74 | //Set index cell center show 75 | if((item.index - 1 == 0) && label.xalign != 0.5f) 76 | { 77 | label.xalign = 0.5f; 78 | } 79 | 80 | label.label = text; 81 | }); 82 | 83 | this.factory.setup.connect((listItem)=>{ 84 | var label = new Label(""); 85 | 86 | label.xalign = 0f; 87 | //Set single show model 88 | label.single_line_mode = true; 89 | //Add custom class name 90 | label.add_css_class("table-cell"); 91 | 92 | listItem.child = label; 93 | }); 94 | 95 | this.factory.teardown.connect((listItem)=>{ 96 | listItem.child = null; 97 | }); 98 | 99 | this.tableView.model = this.selection; 100 | 101 | this.loadTableData(1); 102 | } 103 | 104 | [GtkCallback] 105 | private void nextPage() 106 | { 107 | this.loadTableData(1); 108 | } 109 | 110 | [GtkCallback] 111 | private void prePage() 112 | { 113 | this.loadTableData(-1); 114 | } 115 | 116 | [GtkCallback] 117 | private void refresh() 118 | { 119 | this.loadTableData(0,true); 120 | } 121 | 122 | [GtkCallback] 123 | private async void showDDL() 124 | { 125 | if(Application.ctx.tabExist(TabScheme.DDL,this.pathStr) != -1) 126 | { 127 | return; 128 | } 129 | 130 | Application.ctx.addTab(new DDNotebook(this.pathStr,this.view)); 131 | } 132 | 133 | private async void loadTableData(int offset,bool flush=false) 134 | { 135 | var tab = this.tab(); 136 | 137 | if(tab.loading) 138 | { 139 | return; 140 | } 141 | 142 | var page = this.pageQuery.page; 143 | 144 | var temp = (page + offset); 145 | 146 | temp = (temp <= 0 ? 1 : temp); 147 | 148 | if(!flush && temp == page) 149 | { 150 | return; 151 | } 152 | 153 | tab.loadStatus(true); 154 | 155 | this.listStore.remove_all(); 156 | 157 | FXError error = null; 158 | Gee.List columns = null; 159 | SourceFunc callback = loadTableData.callback; 160 | 161 | int64 total = 0; 162 | 163 | this.pageQuery.inspect(temp,this.wEntry.text, this.sEntry.text); 164 | 165 | var work = AsyncWork.create(()=>{ 166 | var connect = Application.getConnection(uuid); 167 | try 168 | { 169 | list = connect.pageQuery(this.pageQuery); 170 | total = connect.pageCount(this.pageQuery); 171 | columns = connect.tableColumns(this.pageQuery.schema,this.pageQuery.table); 172 | } 173 | catch(FXError e) 174 | { 175 | error = e; 176 | } 177 | finally 178 | { 179 | connect.close(); 180 | Idle.add(callback); 181 | } 182 | }); 183 | work.execute(); 184 | 185 | yield; 186 | 187 | this.rowNLum.label = "%s %s".printf(total.to_string(),_("_Rows")); 188 | 189 | if(error == null) 190 | { 191 | var colNum = columns.size; 192 | 193 | this.diffCol(columns); 194 | var dSize = this.list.size; 195 | var rowNum = dSize / colNum; 196 | for (int j = 0; j < rowNum; j++) 197 | { 198 | //重置偏移量 199 | offset = j * colNum; 200 | 201 | var array = new string[colNum+1]; 202 | 203 | //添加序号列 204 | array[0] = (j + 1).to_string(); 205 | 206 | for (int i = offset; i < offset + colNum; i++) 207 | { 208 | array[i - offset + 1] = this.list.get(i); 209 | } 210 | 211 | this.listStore.append(new TableRowMeta(array)); 212 | 213 | //一次性加载80条数据 214 | if(dSize > 80 && j % 80 == 0) 215 | { 216 | Idle.add(callback); 217 | yield; 218 | } 219 | } 220 | } 221 | else 222 | { 223 | FXAlert.error(_("_Loading fail"),error.message); 224 | } 225 | 226 | this.total = total; 227 | 228 | tab.loadStatus(false); 229 | 230 | this.list.clear(); 231 | } 232 | 233 | /** 234 | * 235 | * 236 | * 差分检查列 237 | * 238 | **/ 239 | private void diffCol(Gee.List list) 240 | { 241 | //创建索引列 242 | if(this.columns.size == 0) 243 | { 244 | this.createColumn("",false,40); 245 | } 246 | 247 | var index = 0; 248 | var newSize = list.size; 249 | var size = this.columns.size; 250 | 251 | foreach(var column in list) 252 | { 253 | var name = column.name; 254 | 255 | if(index < size-1) 256 | { 257 | var ccolumn = this.columns.get(index+1); 258 | if(ccolumn.title != name) 259 | { 260 | ccolumn.title = name; 261 | } 262 | } 263 | else 264 | { 265 | this.createColumn(name,true,150); 266 | } 267 | index++; 268 | } 269 | 270 | //移除多余列 271 | for(var i = (size - 1) ; i >= newSize + 1 ; i--) 272 | { 273 | var column = this.columns.remove_at(i); 274 | this.tableView.remove_column(column); 275 | } 276 | } 277 | 278 | /** 279 | * 280 | * 创建列 281 | * 282 | */ 283 | private void createColumn(string title,bool resizable,int fixWidth) 284 | { 285 | var ccolumn = new ColumnViewColumn 286 | ( 287 | title, 288 | factory 289 | ); 290 | 291 | ccolumn.resizable = resizable; 292 | ccolumn.fixed_width = fixWidth; 293 | 294 | this.columns.add(ccolumn); 295 | this.tableView.append_column(ccolumn); 296 | } 297 | 298 | public NotebookTab tab() 299 | { 300 | return this.notebookTab; 301 | } 302 | 303 | public string path() 304 | { 305 | return TabScheme.toFullPath(TabScheme.TABLE,this.pathStr); 306 | } 307 | 308 | public unowned Gtk.Widget content() 309 | { 310 | return this; 311 | } 312 | 313 | public TabScheme scheme() 314 | { 315 | return TabScheme.TABLE; 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/widget/sql-source-view.vala: -------------------------------------------------------------------------------- 1 | using GtkSource; 2 | 3 | public class SQLSourceView : View 4 | { 5 | construct 6 | { 7 | var buffer = this.buffer as Buffer; 8 | 9 | var lmanager = LanguageManager.get_default(); 10 | var smanager = StyleSchemeManager.get_default(); 11 | 12 | var language = lmanager.get_language("sql"); 13 | var styleScheme = smanager.get_scheme("monokai-extended"); 14 | 15 | 16 | 17 | if(language == null) 18 | { 19 | warning("SQL language not exist?"); 20 | } 21 | else 22 | { 23 | buffer.language = language; 24 | } 25 | 26 | if(styleScheme == null) 27 | { 28 | warning("monokai-extended style scheme not exist?"); 29 | } 30 | else 31 | { 32 | buffer.style_scheme = styleScheme; 33 | } 34 | 35 | this.hexpand = true; 36 | this.vexpand = true; 37 | this.top_margin = 12; 38 | this.monospace = true; 39 | this.left_margin = 12; 40 | this.right_margin = 12; 41 | this.auto_indent = true; 42 | this.bottom_margin = 12; 43 | this.show_line_numbers = true; 44 | this.highlight_current_line = true; 45 | } 46 | } -------------------------------------------------------------------------------- /src/widget/sqlite-compact.vala: -------------------------------------------------------------------------------- 1 | using Gtk; 2 | 3 | [GtkTemplate(ui="/cn/navclub/ldbfx/ui/sqlite-compact.xml")] 4 | public class SqliteCompact : Box,DataSourceConpact 5 | { 6 | 7 | private ConnectDialog dialog; 8 | 9 | [GtkChild] 10 | private unowned Entry fileEntry; 11 | 12 | public SqliteCompact(ConnectDialog dialog) 13 | { 14 | this.dialog = dialog; 15 | } 16 | /** 17 | * 18 | * 表单验证 19 | * 20 | **/ 21 | public override bool formValid() 22 | { 23 | var path = this.fileEntry.text.strip(); 24 | 25 | stdout.printf("%s\n",path!=""?"true":"false"); 26 | var pass = UIUtil.formValid(this.fileEntry,()=>path != ""); 27 | 28 | return pass; 29 | } 30 | /** 31 | * 32 | * 获取当前ui组件 33 | * 34 | */ 35 | public override Widget content() 36 | { 37 | return this; 38 | } 39 | 40 | 41 | /** 42 | * 43 | * 将当前表单内容转换为 DataSource 模型 44 | * 45 | **/ 46 | public override bool toDataSource(DataSource dataSource) 47 | { 48 | var pass = this.formValid(); 49 | if(pass) 50 | { 51 | dataSource.dbFilePath = this.fileEntry.text; 52 | } 53 | return pass; 54 | } 55 | 56 | 57 | public override void initCompact(DataSource dataSource) 58 | { 59 | this.fileEntry.text = dataSource.dbFilePath; 60 | } 61 | 62 | [GtkCallback] 63 | public void openFileChooser() 64 | { 65 | this.dialog.modal = false; 66 | 67 | var fileChooser = new FileChooserDialog("Please choose sqlite file",null,FileChooserAction.OPEN,_("_Select"),1,_("_Cancel"),0,null); 68 | var fileFilter = new FileFilter(); 69 | 70 | fileFilter.add_pattern("*.db"); 71 | fileChooser. select_multiple = false; 72 | fileFilter.set_filter_name(".db file"); 73 | 74 | fileChooser.add_filter(fileFilter); 75 | fileChooser.response.connect((id)=>{ 76 | //IF cancel was actived,direct close FileChooser 77 | if(id == 1) 78 | { 79 | var file = fileChooser.get_file(); 80 | if(file == null) 81 | { 82 | return; 83 | } 84 | this.fileEntry.text = file.get_path(); 85 | } 86 | fileChooser.close(); 87 | this.dialog.modal = true; 88 | }); 89 | 90 | fileChooser.visible = true; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /vapi/config.vapi: -------------------------------------------------------------------------------- 1 | public const string VERSION; 2 | public const string LOCALEDIR; 3 | public const string CONFIG_DIR; 4 | public const string PROJECT_NAME; 5 | public const string SYSTEM_TYPE; 6 | //应用id 7 | public const string APPLICATION_ID; 8 | public const string GETTEXT_PACKAGE; 9 | //图标搜索路径 10 | public const string ICON_SEARCH_PATH; 11 | -------------------------------------------------------------------------------- /vapi/libmariadb.vapi: -------------------------------------------------------------------------------- 1 | /* mysql.vala 2 | * 3 | * Copyright (C) 2008, 2010 Jukka-Pekka Iivonen 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | * 19 | * Author: 20 | * Jukka-Pekka Iivonen 21 | */ 22 | 23 | [CCode (lower_case_cprefix = "mysql_", cheader_filename = "mysql.h")] 24 | namespace Mysql { 25 | [CCode (cname = "unsigned long", cprefix = "CLIENT_", has_type_id = false)] 26 | public enum ClientFlag { 27 | LONG_PASSWORD, 28 | FOUND_ROWS, 29 | LONG_FLAG, 30 | CONNECT_WITH_DB, 31 | NO_SCHEMA, 32 | COMPRESS, 33 | ODBC, 34 | LOCAL_FILES, 35 | IGNORE_SPACE, 36 | PROTOCOL_41, 37 | INTERACTIVE, 38 | SSL, 39 | IGNORE_SIGPIPE, 40 | TRANSACTIONS, 41 | RESERVED, 42 | SECURE_CONNECTION, 43 | MULTI_STATEMENTS, 44 | MULTI_RESULTS, 45 | SSL_VERIFY_SERVER_CERT, 46 | REMEMBER_OPTIONS 47 | } 48 | 49 | [CCode (cname = "enum_mysql_set_option", cprefix = "MYSQL_OPTION_", has_type_id = false)] 50 | public enum SetOption { 51 | MULTI_STATEMENTS_ON, 52 | MULTI_STATEMENTS_OFF 53 | } 54 | 55 | [CCode (cname = "mysql_option", cprefix = "MYSQL_", has_type_id = false)] 56 | public enum Option { 57 | OPT_CONNECT_TIMEOUT, 58 | OPT_COMPRESS, 59 | OPT_NAMED_PIPE, 60 | INIT_COMMAND, 61 | READ_DEFAULT_FILE, 62 | READ_DEFAULT_GROUP, 63 | SET_CHARSET_DIR, 64 | SET_CHARSET_NAME, 65 | OPT_LOCAL_INFILE, 66 | OPT_PROTOCOL, 67 | SHARED_MEMORY_BASE_NAME, 68 | OPT_READ_TIMEOUT, 69 | OPT_WRITE_TIMEOUT, 70 | OPT_USE_RESULT, 71 | OPT_USE_REMOTE_CONNECTION, 72 | OPT_USE_EMBEDDED_CONNECTION, 73 | OPT_GUESS_CONNECTION, 74 | SET_CLIENT_IP, 75 | SECURE_AUTH, 76 | REPORT_DATA_TRUNCATION, 77 | OPT_RECONNECT, 78 | OPT_SSL_VERIFY_SERVER_CERT 79 | } 80 | 81 | [CCode (cname = "enum enum_field_types", cprefix = "MYSQL_TYPE_", has_type_id = false)] 82 | public enum FieldType { 83 | DECIMAL, 84 | TINY, 85 | SHORT, 86 | LONG, 87 | FLOAT, 88 | DOUBLE, 89 | NULL, 90 | TIMESTAMP, 91 | LONGLONG, 92 | INT24, 93 | DATE, 94 | TIME, 95 | DATETIME, 96 | YEAR, 97 | NEWDATE, 98 | VARCHAR, 99 | BIT, 100 | NEWDECIMAL, 101 | ENUM, 102 | SET, 103 | TINY_BLOB, 104 | MEDIUM_BLOB, 105 | LONG_BLOB, 106 | BLOB, 107 | VAR_STRING, 108 | STRING, 109 | GEOMETRY 110 | } 111 | 112 | [CCode (cname = "guint", has_type_id = false)] 113 | public enum FieldFlag { 114 | [CCode (cname = "NOT_NULL_FLAG")] 115 | NOT_NULL, 116 | [CCode (cname = "PRI_KEY_FLAG")] 117 | PRI_KEY, 118 | [CCode (cname = "UNIQUE_FLAG")] 119 | UNIQUE_KEY, 120 | [CCode (cname = "MULTIPLE_KEY_FLAG")] 121 | MULTIPLE_KEY, 122 | [CCode (cname = "BLOB_FLAG")] 123 | BLOB, 124 | [CCode (cname = "UNSIGNED_FLAG")] 125 | UNSIGNED, 126 | [CCode (cname = "ZEROFILL_FLAG")] 127 | ZEROFILL, 128 | [CCode (cname = "BINARY_FLAG")] 129 | BINARY, 130 | [CCode (cname = "ENUM_FLAG")] 131 | ENUM, 132 | [CCode (cname = "AUTO_INCREMENT_FLAG")] 133 | AUTO_INCREMENT, 134 | [CCode (cname = "TIMESTAMP_FLAG")] 135 | TIMESTAMP, 136 | [CCode (cname = "SET_FLAG")] 137 | SET, 138 | [CCode (cname = "NO_DEFAULT_VALUE_FLAG")] 139 | NO_DEFAULT_VALUE, 140 | [CCode (cname = "ON_UPDATE_NOW_FLAG")] 141 | ON_UPDATE_NOW, 142 | [CCode (cname = "NUM_FLAG")] 143 | NUM, 144 | [CCode (cname = "PART_KEY_FLAG")] 145 | PART_KEY, 146 | [CCode (cname = "GROUP_FLAG")] 147 | GROUP, 148 | [CCode (cname = "UNIQUE_FLAG")] 149 | UNIQUE, 150 | [CCode (cname = "BINCMP_FLAG")] 151 | BINCMP, 152 | [CCode (cname = "GET_FIXED_FIELDS_FLAG")] 153 | GET_FIXED_FIELDS, 154 | [CCode (cname = "FIELD_IN_PART_FUNC_FLAG")] 155 | FIELD_IN_PART_FUNC, 156 | [CCode (cname = "FIELD_IN_ADD_INDEX")] 157 | FIELD_IN_ADD_INDEX, 158 | [CCode (cname = "FIELD_IS_RENAMED")] 159 | FIELD_IS_RENAMED 160 | } 161 | 162 | [CCode (cname = "enum_cursor_type", cprefix = "CURSOR_TYPE_", has_type_id = false)] 163 | public enum CursorType { 164 | NO_CURSOR, 165 | READ_ONLY, 166 | FOR_UPDATE, 167 | SCROLLABLE 168 | } 169 | 170 | [Compact] 171 | [CCode (free_function = "mysql_close", cname = "MYSQL", cprefix = "mysql_")] 172 | public class Database { 173 | [CCode (cname = "mysql_init")] 174 | public Database (Database? mysql = null); 175 | 176 | public ulong affected_rows (); 177 | public bool autocommit (bool mode); 178 | public bool change_user (string username, string passwd, string? dbname = null); 179 | public unowned string character_set_name (); 180 | public bool commit (); 181 | public int dump_debug_info (); 182 | public uint errno (); 183 | public unowned string error (); 184 | public unowned string get_host_info (); 185 | public uint get_proto_info (); 186 | public unowned string get_server_info (); 187 | public ulong get_server_version (); 188 | public unowned string get_ssl_cipher (); 189 | public unowned string info (); 190 | public ulong insert_id (); 191 | public int kill (ulong pid); 192 | public Result? list_dbs (string? wild = null); 193 | public Result? list_fields (string table, string? wild = null); 194 | public Result? list_processes (); 195 | public Result? list_tables (string? wild = null); 196 | public bool more_results (); 197 | public int next_result (); 198 | public int options (Option option, string arg); 199 | public int ping (); 200 | public int query (string stmt_str); 201 | public bool real_connect (string? host = null, string? username = null, string? passwd = null, string? dbname = null, uint port = 0, string? unix_socket = null, ClientFlag client_flag = 0); 202 | public ulong real_escape_string (string to, string from, ulong length); 203 | public int real_query (string query, ulong len); 204 | public int reload (); 205 | public bool rollback (); 206 | public int select_db (string dbname); 207 | public int set_character_set (string csname); 208 | public void set_local_infile_default (); 209 | public int set_server_option (SetOption option); 210 | public unowned string sqlstate (); 211 | public int shutdown (int shutdown_level); 212 | public bool ssl_set (string key, string cert, string ca, string capath, string cipher); 213 | public unowned string stat (); 214 | public Result? store_result (); 215 | public ulong thread_id (); 216 | public Result? use_result (); 217 | public uint warning_count (); 218 | } 219 | 220 | [Compact] 221 | [CCode (free_function = "mysql_free_result", cname = "MYSQL_RES", cprefix = "mysql_")] 222 | public class Result { 223 | public bool eof (); 224 | public Field* fetch_field (); 225 | public Field* fetch_field_direct (uint field_nbr); 226 | 227 | [CCode (cname = "mysql_fetch_fields", array_length = false)] 228 | public unowned Field[] _fetch_fields (); 229 | [CCode (cname = "_vala_mysql_fetch_fields")] 230 | public unowned Field[] fetch_fields () { 231 | unowned Field[] fields = this._fetch_fields (); 232 | fields.length = (int) this.num_fields (); 233 | return fields; 234 | } 235 | 236 | [CCode (cname = "mysql_fetch_lengths", array_length = false)] 237 | public unowned ulong[] _fetch_lengths (); 238 | [CCode (cname = "_vala_mysql_fetch_lengths")] 239 | public unowned ulong[] fetch_lengths () { 240 | unowned ulong[] lengths = this._fetch_lengths (); 241 | lengths.length = (int) this.num_fields (); 242 | return lengths; 243 | } 244 | 245 | [CCode (cname = "mysql_fetch_row", array_length = false)] 246 | public unowned string[]? _fetch_row (); 247 | [CCode (cname = "_vala_mysql_fetch_row")] 248 | public unowned string[]? fetch_row () { 249 | unowned string[]? row = this._fetch_row (); 250 | row.length = (int) this.num_fields (); 251 | return row; 252 | } 253 | 254 | public uint fetch_count (); 255 | public uint num_fields (); 256 | public uint num_rows (); 257 | 258 | public bool data_seek (ulong offset); 259 | } 260 | [CCode (cname = "MYSQL_FIELD", has_type_id = false)] 261 | public struct Field { 262 | public unowned string name; 263 | public unowned string org_name; 264 | public unowned string table; 265 | public unowned string org_table; 266 | public unowned string db; 267 | public unowned string catalog; 268 | public unowned string def; 269 | public ulong length; 270 | public ulong max_length; 271 | public uint name_length; 272 | public uint org_name_length; 273 | public uint table_length; 274 | public uint org_table_length; 275 | public uint db_length; 276 | public uint catalog_length; 277 | public uint def_length; 278 | public uint flags; 279 | public uint decimals; 280 | public uint charsetnr; 281 | public FieldType type; 282 | public void *extension; 283 | } 284 | 285 | public unowned string get_client_info (); 286 | public ulong get_client_version (); 287 | public void debug (string msg); 288 | public ulong hex_string (string to, string from, ulong length); 289 | public void library_end (); 290 | public int library_init ([CCode (array_length_pos = 0.1)] string[] argv, [CCode (array_length = false, array_null_terminated = true)] string[]? groups = null); 291 | public void server_end (); 292 | public int server_init ([CCode (array_length_pos = 0.1)] string[] argv, [CCode (array_length = false, array_null_terminated = true)] string[]? groups = null); 293 | public void thread_end (); 294 | public bool thread_init (); 295 | public uint thread_safe (); 296 | } 297 | 298 | --------------------------------------------------------------------------------