Skip to content

Commit 1a56f11

Browse files
authored
Add JSON backend (#97)
1 parent b3b6c07 commit 1a56f11

34 files changed

Lines changed: 1249 additions & 780 deletions

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ ifeq ($(DBPATH),$(DATABASE))
5555
@rm -f $(DBPATH)
5656
@cp -r resources/database $(DIST_DIR)
5757
cat resources/database/database.sql | sed "s|{DIST_PATH}|$(DIST_PATH)|g" | sqlite3 $(DBPATH)
58+
cat resources/database/json.sql | sqlite3 $(DBPATH) > $(patsubst %.db,%.json,$(DBPATH))
5859
endif
5960

6061
# Lint

resources/database/json.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
SELECT json_object(
2+
'version', 2,
3+
'entries', json_group_array(
4+
json_object(
5+
'type', type,
6+
'content', content,
7+
'pinned', pinned,
8+
'tag', tag,
9+
'datetime', strftime('%FT%T.000000Z', datetime, 'utc'),
10+
'metadata', metadata
11+
)
12+
)
13+
) FROM clipboard;

resources/schemas/org.gnome.shell.extensions.copyous.gschema.xml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<?xml-model href="https://gitlab.gnome.org/GNOME/glib/-/raw/HEAD/gio/gschema.dtd"?>
33
<schemalist>
4+
<enum id="org.gnome.shell.extensions.copyous.DatabaseBackend">
5+
<value nick="default" value="0"/>
6+
<value nick="memory" value="1"/>
7+
<value nick="sqlite" value="2"/>
8+
<value nick="json" value="3"/>
9+
</enum>
10+
411
<enum id="org.gnome.shell.extensions.copyous.ClipboardHistory">
512
<value nick="clear" value="0"/>
613
<value nick="keep-pinned-and-tagged" value="1"/>
@@ -93,11 +100,15 @@
93100
<!-- History -->
94101
<key name="in-memory-database" type="b">
95102
<default>false</default>
96-
<summary>Store clipboard history in memory instead of on disk</summary>
103+
<summary>Store clipboard history in memory instead of on disk (Deprecated)</summary>
104+
</key>
105+
<key name="database-backend" enum="org.gnome.shell.extensions.copyous.DatabaseBackend">
106+
<default>'default'</default>
107+
<summary>Selects which backend to use for the clipboard database.</summary>
97108
</key>
98109
<key name="database-location" type="s">
99110
<default>''</default>
100-
<summary>Path to the database. When empty defaults to {XDG_DATA_HOME}/{EXTENSION UUID}/clipboard.db</summary>
111+
<summary>Path to the database. When empty defaults to {XDG_DATA_HOME}/{EXTENSION UUID}/clipboard.{db,json} depending on the backend.</summary>
101112
</key>
102113
<key name="clipboard-history" enum="org.gnome.shell.extensions.copyous.ClipboardHistory">
103114
<default>'keep-pinned-and-tagged'</default>

src/extension.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import type { LanguageFn } from 'highlight.js';
99
import { ClipboardHistory, getDataPath, getHljsLanguages, getHljsPath } from './lib/common/constants.js';
1010
import { DbusService } from './lib/common/dbus.js';
1111
import { SoundManager, tryCreateSoundManager } from './lib/common/sound.js';
12+
import { ClipboardEntry } from './lib/database/database.js';
13+
import { ClipboardEntryTracker } from './lib/database/entryTracker.js';
1214
import { ClipboardManager } from './lib/misc/clipboard.js';
13-
import { ClipboardEntry, ClipboardEntryTracker } from './lib/misc/db.js';
1415
import { NotificationManager } from './lib/misc/notifications.js';
1516
import { ShortcutManager } from './lib/misc/shortcuts.js';
1617
import { ThemeManager } from './lib/misc/theme.js';
@@ -105,7 +106,7 @@ export default class CopyousExtension extends Extension {
105106
this.settings.connectObject(
106107
'changed::database-location',
107108
this.initEntryTracker.bind(this),
108-
'changed::in-memory-database',
109+
'changed::database-backend',
109110
this.initEntryTracker.bind(this),
110111
'changed::history-time',
111112
this.initHistoryTimeout.bind(this),
@@ -218,7 +219,7 @@ export default class CopyousExtension extends Extension {
218219
}
219220

220221
private async initEntryTracker() {
221-
if (!this.entryTracker) return;
222+
if (!this.entryTracker || !this.entryTracker.shouldInit) return;
222223

223224
this.clipboardDialog?.clearEntries();
224225
const entries = await this.entryTracker.init();
@@ -301,7 +302,10 @@ export default class CopyousExtension extends Extension {
301302
try {
302303
const environment = GLib.get_environ();
303304
const settings = GLib.environ_getenv(environment, 'DEBUG_COPYOUS_SCHEMA');
304-
if (settings) schema ??= this.metadata['settings-schema'] + '.debug';
305+
if (settings) {
306+
this.getLogger().log('Using debug schema');
307+
schema ??= this.metadata['settings-schema'] + '.debug';
308+
}
305309

306310
return super.getSettings(schema);
307311
} catch {

src/lib/common/actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Gio from 'gi://Gio';
44
import type { ExtensionPreferences } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
55
import type { ConsoleLike, Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
66

7-
import { ClipboardEntry } from '../misc/db.js';
7+
import { ClipboardEntry } from '../database/database.js';
88
import { ColorSpace } from './color.js';
99
import { ItemType, getActionsConfigPath } from './constants.js';
1010

src/lib/common/constants.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ export const ActiveState = {
6464

6565
export type ActiveState = (typeof ActiveState)[keyof typeof ActiveState];
6666

67+
export const DatabaseBackend = {
68+
Default: 0,
69+
Memory: 1,
70+
Sqlite: 2,
71+
Json: 3,
72+
};
73+
74+
export type DatabaseBackend = (typeof DatabaseBackend)[keyof typeof DatabaseBackend];
75+
6776
export const ClipboardHistory = {
6877
Clear: 0,
6978
KeepPinnedAndTagged: 1,
@@ -309,6 +318,12 @@ export function getActionsConfigPath(ext: Extension | ExtensionPreferences): Gio
309318
return getConfigPath(ext).get_child('actions.json');
310319
}
311320

321+
export function getDefaultDatabaseFile(ext: Extension | ExtensionPreferences, backend: DatabaseBackend): Gio.File {
322+
return backend === DatabaseBackend.Json
323+
? getDataPath(ext).get_child('clipboard.json')
324+
: getDataPath(ext).get_child('clipboard.db');
325+
}
326+
312327
export function getHljsPath(ext: Extension | ExtensionPreferences): Gio.File {
313328
// Check system install
314329
const sysPath = ext.dir.get_child('highlight.min.js');

src/lib/database/database.ts

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import GLib from 'gi://GLib';
2+
import GObject from 'gi://GObject';
3+
4+
import { ClipboardHistory, ItemType, Tag } from '../common/constants.js';
5+
import { int32ParamSpec, registerClass } from '../common/gjs.js';
6+
7+
/**
8+
* Metadata.
9+
*/
10+
export type Metadata = CodeMetadata | FileMetadata | LinkMetadata;
11+
12+
/**
13+
* Programming language.
14+
*/
15+
export interface Language {
16+
id: string;
17+
name: string;
18+
}
19+
20+
/**
21+
* Code metadata.
22+
*/
23+
export interface CodeMetadata {
24+
language: Language | null;
25+
}
26+
27+
/**
28+
* File operation.
29+
*/
30+
export const FileOperation = {
31+
Copy: 'copy',
32+
Cut: 'cut',
33+
} as const;
34+
35+
export type FileOperation = (typeof FileOperation)[keyof typeof FileOperation];
36+
37+
/**
38+
* File metadata.
39+
*/
40+
export interface FileMetadata {
41+
operation: FileOperation;
42+
}
43+
44+
/**
45+
* Link metadata.
46+
*/
47+
export interface LinkMetadata {
48+
title: string | null;
49+
description: string | null;
50+
image: string | null;
51+
}
52+
53+
/**
54+
* Clipboard entry.
55+
*/
56+
@registerClass({
57+
Properties: {
58+
id: int32ParamSpec('id', GObject.ParamFlags.READABLE, 0),
59+
type: GObject.ParamSpec.string('type', null, null, GObject.ParamFlags.READWRITE, ItemType.Text),
60+
content: GObject.ParamSpec.string('content', null, null, GObject.ParamFlags.READWRITE, ''),
61+
pinned: GObject.ParamSpec.boolean('pinned', null, null, GObject.ParamFlags.READWRITE, false),
62+
tag: GObject.ParamSpec.string('tag', null, null, GObject.ParamFlags.READWRITE, ''),
63+
datetime: GObject.ParamSpec.boxed('datetime', null, null, GObject.ParamFlags.READWRITE, GLib.DateTime),
64+
metadata: GObject.ParamSpec.jsobject('metadata', null, null, GObject.ParamFlags.READWRITE),
65+
title: GObject.ParamSpec.string('title', null, null, GObject.ParamFlags.READWRITE, ''),
66+
},
67+
Signals: {
68+
delete: {},
69+
},
70+
})
71+
export class ClipboardEntry extends GObject.Object {
72+
private readonly _id: number;
73+
declare type: ItemType;
74+
declare content: string;
75+
declare pinned: boolean;
76+
declare tag: Tag | null;
77+
declare datetime: GLib.DateTime;
78+
declare metadata: Metadata | null;
79+
declare title: string;
80+
81+
constructor(
82+
id: number,
83+
type: ItemType,
84+
content: string,
85+
pinned: boolean,
86+
tag: Tag | null,
87+
datetime: GLib.DateTime,
88+
metadata: Metadata | null = null,
89+
title: string = '',
90+
) {
91+
super();
92+
93+
this._id = id;
94+
this.type = type;
95+
this.content = content;
96+
this.pinned = pinned;
97+
this.tag = tag;
98+
this.datetime = datetime;
99+
this.metadata = metadata;
100+
this.title = title;
101+
}
102+
103+
get id() {
104+
return this._id;
105+
}
106+
}
107+
108+
/**
109+
* Clipboard database
110+
*/
111+
export interface Database {
112+
/**
113+
* Initializes the database
114+
*/
115+
init(): Promise<void>;
116+
117+
/**
118+
* Clears the database.
119+
* @param history Which items to keep.
120+
*/
121+
clear(history: ClipboardHistory): Promise<number[]>;
122+
123+
/**
124+
* Close the connection to the database.
125+
*/
126+
close(): Promise<void>;
127+
128+
/**
129+
* Gets the entries of the database.
130+
*/
131+
entries(): Promise<ClipboardEntry[]>;
132+
133+
/**
134+
* Select a conflicting entry by its type and content.
135+
* @param entry The entry to search its conflict for.
136+
* @returns The id of the conflicting entry or null if no conflict was found.
137+
*/
138+
selectConflict(entry: ClipboardEntry | { type: ItemType; content: string }): Promise<number | null>;
139+
140+
/**
141+
* Inserts an entry into the database.
142+
* @param type The type of the entry.
143+
* @param content The content of the entry.
144+
* @param metadata Metadata of the entry.
145+
* @returns The inserted entry or null if the insertion failed.
146+
*/
147+
insert(type: ItemType, content: string, metadata: Metadata | null): Promise<ClipboardEntry | null>;
148+
149+
/**
150+
* Updates a property of an inserted database entry.
151+
* @param entry The entry to update the property of.
152+
* @param property The property of the entry to update.
153+
* @returns -1 if the property was updated, the id of a conflicting entry otherwise.
154+
*/
155+
updateProperty<K extends Exclude<keyof ClipboardEntry, keyof GObject.Object | 'id'>>(
156+
entry: ClipboardEntry,
157+
property: K,
158+
): Promise<number>;
159+
160+
/**
161+
* Delete an entry from the database.
162+
* @param entry The entry to delete.
163+
* @returns true if the entry was deleted, false otherwise.
164+
*/
165+
delete(entry: ClipboardEntry): Promise<boolean>;
166+
167+
/**
168+
* Delete the oldest entries of the database.
169+
* @param offset The number of entries to keep.
170+
* @param olderThanMinutes Items older than this value will be deleted.
171+
* @returns The ids of entries that were deleted.
172+
*/
173+
deleteOldest(offset: number, olderThanMinutes: number): Promise<number[]>;
174+
}

0 commit comments

Comments
 (0)