Compare commits

..

143 Commits

Author SHA1 Message Date
20dc34714e Merge pull request '5.1.0: add player life cycle events' (#33) from dev into master
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 2m53s
Test and Deploy / deploy (17, ubuntu-latest) (push) Successful in 4m4s
Reviewed-on: #33
2024-08-20 18:52:18 +02:00
267bd3644c ci: add cache
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 6m12s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
Test and Deploy / build (17, ubuntu-latest) (pull_request) Successful in 2m26s
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been skipped
2024-08-20 18:42:39 +02:00
d6515d9cbb feat: add player join and leave events
Some checks failed
Test and Deploy / deploy (17, ubuntu-latest) (push) Blocked by required conditions
Test and Deploy / build (17, ubuntu-latest) (push) Has been cancelled
2024-08-20 18:39:30 +02:00
5e6eff241e Merge pull request '5.0.0' (#32) from dev into master
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 3m50s
Test and Deploy / deploy (17, ubuntu-latest) (push) Successful in 4m5s
Reviewed-on: #32
2024-02-12 20:49:49 +01:00
204198f143 Remove deprecated method and remove redundant cast or throws declaration
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 3m55s
Test and Deploy / build (17, ubuntu-latest) (pull_request) Successful in 4m7s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been skipped
2024-02-12 20:40:44 +01:00
9fdc4f4991 Update okhttp to fix vulnerability 2024-02-12 20:33:20 +01:00
b88d7f3e08 ConfigI#load now return given clazz in parameter, dump to 5.0.0 because of breaking changes
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 3m55s
Test and Deploy / build (17, ubuntu-latest) (pull_request) Successful in 3m48s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been skipped
2024-02-12 19:56:37 +01:00
ebbb92f66d Remove my name in contributions and add mod parenting in modmenu, Use a more detailled image of altarik icon
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 3m41s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
2024-02-08 12:47:42 +01:00
6b9d4fa968 Merge pull request 'Dump minecraft to 1.20.4' (#31) from dev into master
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 3m40s
Test and Deploy / deploy (17, ubuntu-latest) (push) Successful in 3m42s
Reviewed-on: #31
2024-02-04 17:54:14 +01:00
041bff1908 Dump minecraft to 1.20.4
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 3m44s
Test and Deploy / build (17, ubuntu-latest) (pull_request) Successful in 3m48s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been skipped
2024-02-04 17:44:39 +01:00
caa52be19e Merge pull request '4.5.0' (#30) from dev into master
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m18s
Test and Deploy / deploy (17, ubuntu-latest) (push) Successful in 4m16s
Reviewed-on: #30
2024-01-08 22:02:00 +01:00
2c38a76c0a Dump to 4.5.0
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m21s
Test and Deploy / build (17, ubuntu-latest) (pull_request) Successful in 4m24s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been skipped
2024-01-08 21:52:52 +01:00
fede46e91e Merge remote-tracking branch 'origin/dev' into dev
Some checks failed
Test and Deploy / deploy (17, ubuntu-latest) (push) Blocked by required conditions
Test and Deploy / build (17, ubuntu-latest) (push) Has been cancelled
2024-01-08 21:49:23 +01:00
95ff56969e Dump to 4.4.0 2024-01-08 21:46:41 +01:00
0a7d61e45e Add configI to abstract config files data and process 2024-01-08 21:46:16 +01:00
a7b2067765 Merge pull request 'Dump to 4.4.0' (#29) from dev into master
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m38s
Test and Deploy / deploy (17, ubuntu-latest) (push) Successful in 4m25s
Reviewed-on: #29
2024-01-07 01:24:28 +01:00
427fc4cea6 Update gradle.properties
Some checks failed
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been cancelled
Test and Deploy / build (17, ubuntu-latest) (push) Has been cancelled
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been cancelled
Test and Deploy / build (17, ubuntu-latest) (pull_request) Has been cancelled
2024-01-07 01:24:09 +01:00
9590cb9aea Merge pull request 'Forgot to add an env instruction...' (#28) from dev into master
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m44s
Test and Deploy / deploy (17, ubuntu-latest) (push) Successful in 4m46s
Reviewed-on: #28
2024-01-07 01:12:09 +01:00
f982e497fe Forgot to add an env instruction...
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m49s
Test and Deploy / build (17, ubuntu-latest) (pull_request) Successful in 4m40s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been skipped
2024-01-07 00:57:32 +01:00
23cd5ca100 Merge pull request 'Fix CI trigger' (#27) from dev into master
Some checks failed
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m26s
Test and Deploy / deploy (17, ubuntu-latest) (push) Failing after 4m20s
Reviewed-on: #27
2024-01-06 23:40:34 +01:00
6fc944eb4a Update .gitea/workflows/ci.yml
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m43s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
Test and Deploy / build (17, ubuntu-latest) (pull_request) Successful in 5m13s
Test and Deploy / deploy (17, ubuntu-latest) (pull_request) Has been skipped
2024-01-06 22:58:46 +01:00
bc3caaed7d Merge pull request '1.20.2' (#26) from dev into master
Some checks failed
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m37s
Test and Deploy / deploy (17, ubuntu-latest) (push) Failing after 4m20s
Reviewed-on: #26
2024-01-06 22:50:29 +01:00
4731cc5698 Update .gitea/workflows/ci.yml
Some checks failed
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m46s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Successful in 6m21s
/ deploy (17, ubuntu-latest) (pull_request) Has been cancelled
2024-01-06 22:28:57 +01:00
6276ba58d7 Update .gitea/workflows/ci.yml
Some checks failed
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 5m11s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Successful in 5m17s
/ deploy (17, ubuntu-latest) (pull_request) Has been cancelled
2024-01-06 22:13:05 +01:00
c3ee389d55 Merge remote-tracking branch 'origin/dev' into dev
Some checks failed
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m47s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Successful in 5m36s
/ deploy (17, ubuntu-latest) (pull_request) Has been cancelled
# Conflicts:
#	build.gradle
#	gradle.properties
2024-01-06 21:47:34 +01:00
0c67018eda Dump to 4.4.0 2024-01-06 21:44:36 +01:00
59161400e8 Update to 1.20.2 2024-01-06 21:42:25 +01:00
c098820076 Remove unnecessary semicolon
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m15s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
2023-08-29 22:50:56 +02:00
40768b2ec1 Added Command and AbstractCommand
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m12s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
2023-08-29 22:50:21 +02:00
10edfe5ef6 Improve CI: now create a tag when publishing a new release
All checks were successful
Test and Deploy / build (17, ubuntu-latest) (push) Successful in 4m21s
Test and Deploy / deploy (17, ubuntu-latest) (push) Has been skipped
2023-08-29 22:44:06 +02:00
be81716cfe Merge pull request 'Trying to fix report discord task' (#24) from dev into master
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m33s
/ deploy (17, ubuntu-latest) (push) Successful in 6m26s
Reviewed-on: #24
2023-07-26 23:53:11 +02:00
3d3851d032 Merge pull request 'Trying to fix report discord task' (#23) from patch-report1 into dev
Some checks reported warnings
/ build (17, ubuntu-latest) (push) Successful in 4m30s
/ deploy (17, ubuntu-latest) (push) Has been skipped
/ deploy (17, ubuntu-latest) (pull_request) Has been cancelled
/ build (17, ubuntu-latest) (pull_request) Has been cancelled
Reviewed-on: #23
2023-07-26 23:46:29 +02:00
c459bf9397 Trying to fix report discord task
All checks were successful
/ build (17, ubuntu-latest) (pull_request) Successful in 4m45s
/ deploy (17, ubuntu-latest) (pull_request) Has been skipped
2023-07-26 23:36:01 +02:00
969eaf92bc Add project name to reportConfig
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 6m53s
/ deploy (17, ubuntu-latest) (push) Has been skipped
2023-07-26 23:33:01 +02:00
662206f32f Merge pull request 'Task reportToDiscord throw an exception if http request is not sucessful' (#22) from dev into master
Some checks failed
/ build (17, ubuntu-latest) (push) Successful in 4m34s
/ deploy (17, ubuntu-latest) (push) Failing after 4m12s
Reviewed-on: #22
2023-07-26 23:25:15 +02:00
bb9ae4d59b Doesn't return an exception if 20X
Some checks reported warnings
/ deploy (17, ubuntu-latest) (push) Has been cancelled
/ build (17, ubuntu-latest) (push) Has been cancelled
/ deploy (17, ubuntu-latest) (pull_request) Has been cancelled
/ build (17, ubuntu-latest) (pull_request) Has been cancelled
2023-07-26 23:22:49 +02:00
18964aa779 Change exception to GradleException
Some checks reported warnings
/ deploy (17, ubuntu-latest) (push) Has been cancelled
/ build (17, ubuntu-latest) (push) Has been cancelled
2023-07-26 23:21:14 +02:00
5afbcd9ffe task throw an exception when response isn't a 200 code
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m58s
/ deploy (17, ubuntu-latest) (push) Has been skipped
2023-07-26 23:11:19 +02:00
7adf20f1ed Merge pull request 'Add a report to discord task' (#21) from dev into master
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 7m4s
/ deploy (17, ubuntu-latest) (push) Successful in 4m52s
Reviewed-on: #21
2023-07-26 22:37:00 +02:00
23236ac2dc Dump to 4.3.2-SNAPSHOT
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 5m5s
/ deploy (17, ubuntu-latest) (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Successful in 5m3s
/ deploy (17, ubuntu-latest) (pull_request) Has been skipped
2023-07-26 22:19:37 +02:00
349ab639da Add a report to discord gradle task and added it to ci
Some checks reported warnings
/ deploy (17, ubuntu-latest) (push) Has been cancelled
/ build (17, ubuntu-latest) (push) Has been cancelled
2023-07-26 22:18:20 +02:00
75217ffe30 Merge pull request 'Fix deploy task' (#20) from dev into master
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m48s
/ deploy (17, ubuntu-latest) (push) Successful in 3m43s
Reviewed-on: #20
2023-07-20 19:31:03 +02:00
3ad8323085 Remove unnecessary semicolon
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m15s
/ deploy (17, ubuntu-latest) (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Successful in 4m11s
/ deploy (17, ubuntu-latest) (pull_request) Has been skipped
2023-07-20 19:17:58 +02:00
362de5e40d Finally, fix deploy task, it works
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m22s
/ deploy (17, ubuntu-latest) (push) Has been skipped
2023-07-20 19:17:19 +02:00
3a2435b1e8 Activate fabric-loom multi project optimisation
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m30s
/ deploy (17, ubuntu-latest) (push) Has been skipped
2023-07-04 19:19:11 +02:00
d995cdd501 Merge pull request 'Improved DataTracker, added registry' (#19) from dev into master
Some checks failed
/ build (17, ubuntu-latest) (push) Successful in 4m52s
/ deploy (17, ubuntu-latest) (push) Failing after 3m49s
Reviewed-on: #19
2023-06-21 18:39:48 +02:00
c74ae48156 Add Registry
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 6m42s
/ deploy (17, ubuntu-latest) (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Failing after 5m47s
/ deploy (17, ubuntu-latest) (pull_request) Has been skipped
2023-06-21 18:15:12 +02:00
285e36e801 If startTracking and set
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 12m7s
/ deploy (17, ubuntu-latest) (push) Has been skipped
2023-06-15 22:08:39 +02:00
3ab417ec45 Added getTrackedDataValueIterator 2023-06-15 21:36:50 +02:00
9049926a83 Added getTrackedDataIterator
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 3m19s
/ deploy (17, ubuntu-latest) (push) Has been skipped
2023-06-15 21:28:53 +02:00
b8c99862d5 Dump to 4.1.1-SNAPSHOT
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 4m27s
/ deploy (17, ubuntu-latest) (push) Has been skipped
2023-06-15 19:54:20 +02:00
5630794fd1 Merge pull request 'fix deploy' (#18) from dev into master
Some checks failed
/ build (17, ubuntu-latest) (push) Successful in 4m38s
/ deploy (17, ubuntu-latest) (push) Failing after 3m57s
Reviewed-on: #18
2023-06-13 22:52:47 +02:00
bbde71463b fix deploy
Some checks reported warnings
/ build (17, ubuntu-latest) (push) Has started running
/ deploy (17, ubuntu-latest) (push) Has been cancelled
/ build (17, ubuntu-latest) (pull_request) Successful in 4m40s
/ deploy (17, ubuntu-latest) (pull_request) Has been skipped
2023-06-13 22:52:03 +02:00
119d019bbb Merge pull request 'Fix deploy' (#17) from dev into master
Some checks failed
/ build (17, ubuntu-latest) (push) Successful in 4m47s
/ deploy (push) Failing after 10s
Reviewed-on: #17
2023-06-13 22:38:35 +02:00
9ee131a1f6 Fix deploy
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m53s
/ deploy (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Successful in 4m39s
/ deploy (pull_request) Has been skipped
2023-06-13 22:28:14 +02:00
a80dac0fe1 Merge branch 'dev'
Some checks failed
/ build (17, ubuntu-latest) (push) Successful in 4m39s
/ deploy (push) Failing after 2s
2023-06-13 22:15:28 +02:00
193236f186 Update '.gitea/workflows/test.yml'
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m47s
/ deploy (push) Has been skipped
/ build (17, ubuntu-latest) (pull_request) Successful in 4m47s
/ deploy (pull_request) Has been skipped
2023-06-13 21:59:12 +02:00
ea37bdb98f Update 'build.gradle'
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m53s
2023-06-13 21:51:54 +02:00
6f9bd14372 Update '.gitea/workflows/test.yml'
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 50s
2023-06-13 21:49:55 +02:00
26d546e229 log message to try to file runner
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 48s
2023-06-13 21:40:11 +02:00
97476da964 log message to try to file runner
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 49s
2023-06-13 21:34:10 +02:00
1184558c31 log message to try to file runner
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 48s
2023-06-13 21:27:06 +02:00
20ba0a126a log message to try to file runner
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 49s
2023-06-13 21:24:24 +02:00
bd8980b8cc log message to try to file runner
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 48s
2023-06-13 21:19:23 +02:00
393da963e6 log message to try to file runner
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 48s
2023-06-13 21:15:55 +02:00
eceba274d8 Fix build config
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 47s
2023-06-13 21:12:34 +02:00
cfb953e8dc Remove secret from gradle.properties, moved it to ENV variables or local.properties
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 46s
2023-06-13 21:02:46 +02:00
81d780f081 Change password to a secret
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 5m3s
2023-06-13 18:13:23 +02:00
7fdbcda1f6 Merge pull request 'Add DataTracker and TrackedData' (#15) from datatracker into dev
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 5m20s
Reviewed-on: #15
2023-06-13 18:08:35 +02:00
7b054ed935 Update fabric.mod.json and fix Pagination name
All checks were successful
/ build (17, ubuntu-latest) (pull_request) Successful in 5m18s
2023-06-13 18:03:13 +02:00
780c0d650c Updated and added fabric.mod.json
All checks were successful
/ build (17, ubuntu-latest) (pull_request) Successful in 4m44s
2023-06-13 17:46:19 +02:00
c976468d2b Add DataTracker and TrackedData
All checks were successful
/ build (17, ubuntu-latest) (pull_request) Successful in 4m49s
2023-06-13 17:36:49 +02:00
ee67f7e075 Added deleteRow and truncateTable for KeyValueTable, add test for KeyValueTable
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m36s
2023-06-08 20:33:35 +02:00
95d06283e4 Moved class from keyvalue to keyValue
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m24s
2023-06-08 18:44:27 +02:00
17cf359e83 KeyValue has been simplified
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m34s
2023-06-08 18:41:59 +02:00
c66067da8c Move builder to Core module and developed it a bit
Some checks failed
/ build (17, ubuntu-latest) (push) Failing after 13m19s
2023-06-08 18:25:21 +02:00
879dd5554d Add comments to KeyValueTable, KeyValueConnection implements KeyValueTable
All checks were successful
/ build (17, ubuntu-latest) (push) Successful in 4m16s
2023-06-05 22:38:53 +02:00
6f05978553 Add KeyValueConnection, update loom to 1.2, gradle to 8.1 2023-06-05 18:42:20 +02:00
48e53e0edb Added support of Text for Pagination
All checks were successful
build (17, ubuntu-latest)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-24 21:58:05 +01:00
351b8aaf84 Add a new api method (not developed yet)
All checks were successful
build (17, ubuntu-latest)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-24 19:56:10 +01:00
30853dee70 Improve player feedback and improve table style
All checks were successful
build (17, ubuntu-latest)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-24 19:38:40 +01:00
5d6cf24582 Change values in PaginatedContentfrom String to Text for future updates
All checks were successful
build (17, ubuntu-latest)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-24 18:41:36 +01:00
088566175f remove deploy job
All checks were successful
build (17, ubuntu-latest)
2023-03-23 22:02:32 +01:00
f038a93796 Remove deploy job
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
2023-03-23 22:01:46 +01:00
25e65e0a98 Update '.gitea/workflows/test.yml'
Some checks failed
build (17, ubuntu-latest)
deploy
2023-03-23 21:55:09 +01:00
e6455a86e0 Remove psql from tests
Some checks failed
build (17, ubuntu-latest)
deploy
2023-03-23 21:48:05 +01:00
2f75bb6083 Exclude test which require a connection to database
Some checks failed
build (17, ubuntu-latest)
deploy
2023-03-23 21:47:04 +01:00
7326fb2a7f Update '.gitea/workflows/test.yml'
Some checks failed
build (17, ubuntu-latest)
deploy
2023-03-23 21:40:00 +01:00
e16843c3e4 Add postgresql to perform databases tests
Some checks failed
build (17, ubuntu-latest)
deploy
2023-03-23 21:32:52 +01:00
4c0ec2ae27 Merge pull request 'pagination' (#10) from pagination into dev
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
deploy
Reviewed-on: #10
2023-03-23 02:16:30 +01:00
d82c8cecad Added some "=" to headers and footers
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
deploy
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-23 02:15:37 +01:00
5992aaa5a0 Added a test command and made some fixes
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-23 01:52:02 +01:00
f2359c75f4 Fix build and reduce memory footprint by fixing max memory usage to 2G instead of 4G
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-23 01:33:03 +01:00
5a1e741eac Add command and page display
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
deploy
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-23 01:12:46 +01:00
dfc9641e66 Merge pull request 'rebase dev to pagination' (#9) from dev into pagination
Reviewed-on: #9
2023-03-21 09:38:14 +01:00
b99bcaa71e Merge pull request 'Fix action' (#7) from dev into master
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
deploy
Reviewed-on: #7
2023-03-21 09:35:09 +01:00
38cf096c0c Merge conflict
Some checks failed
build (17, windows-2022)
deploy
build (17, ubuntu-latest)
2023-03-21 09:34:02 +01:00
116e421dfc fix test.yml
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
deploy
2023-03-21 09:06:14 +01:00
d77c45630d fix test.yml
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
deploy
2023-03-21 09:04:54 +01:00
f61b18ce13 fix test.yml 2023-03-21 09:03:34 +01:00
969f63878b Implement test to master (#6)
Some checks failed
build (17, ubuntu-latest)
build (17, windows-2022)
deploy
Reviewed-on: #6
2023-03-21 09:00:40 +01:00
2ad62de9f7 Fix test 2023-03-21 09:00:01 +01:00
265cb9b891 update test.yml, please action work... 2023-03-21 08:57:52 +01:00
bd89b4cd72 Update '.gitea/workflows/test.yml' 2023-03-21 08:48:56 +01:00
9237dc565e test updated 2023-03-21 08:46:21 +01:00
2570163571 update test 2023-03-21 08:41:15 +01:00
85d616506d update action 2023-03-21 08:38:41 +01:00
50070aeddf update action 2023-03-21 08:35:30 +01:00
ed52206238 Update action 2023-03-21 08:30:25 +01:00
899954f5a5 Update test.yml
Some checks failed
build (17, windows-2022)
deploy
build (17, ubuntu-latest)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-20 21:11:48 +01:00
b9761edda0 Update '.gitea/workflows/test.yml'
Some checks failed
build (17, windows-2022)
deploy
build (17, ubuntu-latest)
2023-03-20 20:51:05 +01:00
270f17c754 Add an action
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-20 20:44:33 +01:00
be9c4de097 Fix HeaderCondition
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-19 19:53:54 +01:00
19e1dd2937 Add pagination precondition 2023-03-19 19:38:08 +01:00
31b6a45ef7 Merge pull request 'Fix mixins and temporary remove Pagination' (#5) from dev into master
Reviewed-on: #5
2023-03-02 18:53:46 +01:00
cbc6796b7c Fix mixins and temporary remove Pagination
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-02 18:50:23 +01:00
7a21af570b Moving all pagination work
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-02 15:07:27 +01:00
16dff9556d Fix task build.gradle
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-02 14:51:17 +01:00
5bb9df3c49 Add artifact with all subprojects inside 2023-03-02 14:07:48 +01:00
24564362ec Add pagination components
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-03-01 23:21:59 +01:00
e3c5d0dd67 Merge pull request 'Dump to 4.0.0' (#4) from 4.0.0 into master
Reviewed-on: #4
2023-02-27 00:17:48 +01:00
0228787568 Add Pagination project
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-27 00:13:09 +01:00
1a4e3481d5 Change list by a queue in order to use a thread-safe non-blocking concurrent queue
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-20 14:19:51 +01:00
10f4ac013e DUmp to 3.0.1-SNAPSHOT
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-14 15:29:26 +01:00
9a2b0182df Remove Jenkinsfile (my vps can't handle it), update dependency declaration
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-14 14:54:14 +01:00
d71ff26617 Merge branch 'master' of https://git.altarik.fr/quentinlegot/Toolbox 2023-02-13 17:51:01 +01:00
97ada864e1 Changing permission of gradlew 2023-02-13 17:50:59 +01:00
0f67867d23 Update 'Jenkinsfile' 2023-02-13 17:41:28 +01:00
50324824c8 Update 'Jenkinsfile' 2023-02-13 17:40:16 +01:00
d161a9d145 Update 'Jenkinsfile' 2023-02-13 17:37:32 +01:00
2eed4d31dc Add Jenkinsfile
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-13 17:13:15 +01:00
553a97fa7a Dump to 3.0.0-SNAPSHOT
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-13 15:31:03 +01:00
f3559bf545 Merge pull request 'fabric-synctask' (#1) from fabric-synctask into master
Reviewed-on: #1
2023-02-13 15:26:37 +01:00
0f902faa1b Fix fabric.mod.json, add run folder to gitignore
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-13 15:25:08 +01:00
f5db55e96e Added tests 2023-02-13 15:00:13 +01:00
c32e72dda6 Move each task scheduler to a specific class: Scheduler.java, AsyncPeriodicTasks.java scheduler is now on server thread (send task to async worker)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-13 14:17:03 +01:00
be9f422f1f Made some twerks to tasks, fix task period for PeriodicSyncTask
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-12 19:10:24 +01:00
24656fa74f add OneTimeSyncTask and started to implement its test (not finished)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-12 14:51:30 +01:00
9123d71a7e Add tests to PeriodicSyncTask
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-12 14:24:09 +01:00
e9c97df089 Fix issue avoiding the mod not to compile, add a test for asyncTasks (still need to be implemented)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-03 23:52:50 +01:00
e7178d44a9 PeriodicSyncTask and AsyncPeriodicTasks should work now (untested)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-03 23:40:01 +01:00
b1ee9344b8 Added AsyncPeriodicTasks, improved a bit PeriodicSyncTask (still a bit of work to do)
Signed-off-by: Quentin Legot <legotquentin@gmail.com>
2023-02-03 17:51:16 +01:00
863952b120 Added periodic task (but unfinished), have to rework a bit AsyncTasks (add a scheduler to add cancellability) 2023-02-03 12:31:51 +01:00
83 changed files with 2451 additions and 99 deletions

82
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,82 @@
# Automatically build the project and run any configured tests for every push
# and submitted pull request. This can help catch issues that only occur on
# certain platforms or Java versions, and provides a first line of defence
# against bad commits.
name: Test and Deploy
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master, dev ]
env:
REPO_USERNAME: Altarik
REPO_PASSWORD: ${{ secrets.REPO_PASSWORD }}
DISCORD_PUB_ID: ${{ secrets.DISCORD_PUB_ID }}
DISCORD_PUB_TOKEN: ${{ secrets.DISCORD_PUB_TOKEN }}
jobs:
build:
strategy:
matrix:
java: [ '17' ]
os: [ ubuntu-latest ]
runs-on: ${{ matrix.os }}
steps:
- name: checkout repository
uses: actions/checkout@v3
- name: validate gradle wrapper
uses: https://github.com/gradle/wrapper-validation-action@v1
- name: setup jdk ${{ matrix.Java }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java }}
distribution: 'oracle'
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: make gradle wrapper executable
if: ${{ runner.os != 'Windows' }}
run: |
chmod +x ./gradlew
touch local.properties
- name: build
run: ./gradlew build # compile classes, testClasses, assemble in jar and javadocJar, and then test
deploy:
strategy:
matrix:
java: [ '17' ]
os: [ ubuntu-latest ]
runs-on: ${{ matrix.os }}
if: ${{ gitea.ref == 'refs/heads/master' && gitea.event_name == 'push' }}
needs: build
steps:
- name: checkout repository
uses: actions/checkout@v3
- name: validate gradle wrapper
uses: https://github.com/gradle/wrapper-validation-action@v1
- name: setup jdk ${{ matrix.Java }}
uses: actions/setup-java@v3
with:
java-version: ${{ matrix.java }}
distribution: 'oracle'
- name: make gradle wrapper executable
if: ${{ runner.os != 'Windows' }}
run: |
chmod +x ./gradlew
touch local.properties
- name: deploy
run: ./gradlew publish
- name: create tag
run: ./gradlew createTag
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Report to Discord
run: ./gradlew reportToDiscord

5
.gitignore vendored
View File

@ -3,6 +3,11 @@ build/
!gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/ !**/src/main/**/build/
!**/src/test/**/build/ !**/src/test/**/build/
local.properties
run/*
**/run
**/logs
### IntelliJ IDEA ### ### IntelliJ IDEA ###
.idea .idea

View File

@ -0,0 +1,51 @@
package fr.altarik.toolbox.core;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Objects;
public abstract class Registry<T, U> {
protected final HashMap<T, U> registry = new HashMap<>();
/**
* Return a set view of the keys contained in the registry.
* @return An iterator view of the keys contained in the registry
*/
public Iterator<T> keySet() {
return registry.keySet().iterator();
}
/**
* Returns true if this registry contains the key.
* @param key the key whose presence in the registry is to be tested
* @return Returns true if this registry contains the key, false otherwise
*/
public boolean containsKey(T key) {
return registry.containsKey(key);
}
/**
* Returns the containing value represented by a specific key, or {@code null} if the registry do not contain the key
* @param key the key whose associated value is to be returned
* @return The value to which the specified key is represented, or {@code null} if the registry do not contain the key
*/
public @Nullable U getValue(T key) {
return registry.get(key);
}
/**
* If the specified key is not already associated with a value, associate it with the given value.
* @param key Key to which the specified value is to be associated
* @param value value to be associated with the specified key
* @throws NullPointerException if key or value is null
*/
public void register(@NotNull T key, @NotNull U value) {
registry.putIfAbsent(Objects.requireNonNull(key), Objects.requireNonNull(value));
}
}

View File

@ -0,0 +1,8 @@
package fr.altarik.toolbox.core.builder;
public class EmptyCollectionException extends NullPointerException {
public EmptyCollectionException(String message) {
super(message);
}
}

View File

@ -0,0 +1,11 @@
package fr.altarik.toolbox.core.builder;
public interface IBuilder<T> {
/**
* Build the builders parameters into T object
* @return The created objects thanks to given parameters
* @throws Exception if any error occur during creation of the built object
*/
T build() throws Exception;
}

View File

@ -0,0 +1,24 @@
package fr.altarik.toolbox.core.builder;
/**
* Builder parameter, for more flexibility
* @param <T> the parameter type
* @see OptionalParamBuilder
* @see RequiredParamBuilder
* @see RequiredCollectionParameterBuilder
*/
public interface IParamBuilder<T> {
/**
* Get the given object, may return {@link NullPointerException} depending on the policy of implemented class
* @return the parameter given by {@link IParamBuilder#set(Object)}
* @throws NullPointerException may throw this error depending on the policy of implemented class
*/
T get() throws NullPointerException;
/**
* Change/insert the value of the parameter
* @param parameter the given parameter
*/
void set(T parameter);
}

View File

@ -0,0 +1,24 @@
package fr.altarik.toolbox.core.builder;
/**
* Doesn't throw a {@link NullPointerException} when using {@link IParamBuilder#get()} in any case
* @param <T> the returned type
* @see IParamBuilder
*/
public class OptionalParamBuilder<T> implements IParamBuilder<T> {
private T parameter;
public OptionalParamBuilder(T param) {
this.parameter = param;
}
@Override
public T get() throws NullPointerException {
return parameter;
}
@Override
public void set(T parameter) {
this.parameter = parameter;
}
}

View File

@ -0,0 +1,52 @@
package fr.altarik.toolbox.core.builder;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Objects;
/**
* Mostly same as {@link RequiredParamBuilder} but for list
* @param <E> The type contained in the collection
* @param <T> The returned type
*/
public class RequiredCollectionParameterBuilder<E, T extends Collection<E>> implements IParamBuilder<T> {
private final T collection;
private final boolean canBeEmpty;
public RequiredCollectionParameterBuilder(@NotNull T collection, boolean canBeEmpty) {
this.collection = Objects.requireNonNull(collection);
this.canBeEmpty = canBeEmpty;
}
/**
* <p>Return the list, if not empty</p>
* <p>If empty, return the collection if {@code canBeEmpty} if true, otherwise throw a {@link NullPointerException}</p>
* @return the collection
* @throws NullPointerException if collection is empty and {@code canBeEmpty} is false
*/
@Override
public T get() throws NullPointerException {
if(canBeEmpty) {
return collection;
} else if(!collection.isEmpty()) {
return collection;
} else {
throw new EmptyCollectionException("Collection cannot be empty");
}
}
@Override
public void set(T parameter) {
throw new UnsupportedOperationException("Use `add` in place of `set` for RequiredCollectionParameterBuilder");
}
/**
* Add an element to the collection
* @param element element to add to the list
*/
public void add(E element) {
collection.add(element);
}
}

View File

@ -0,0 +1,30 @@
package fr.altarik.toolbox.core.builder;
import java.util.Objects;
/**
* Throw a {@link NullPointerException} when using {@link IParamBuilder#get()} if the parameter doesn't have been initialized
* @param <T> the returned type
*/
public class RequiredParamBuilder<T> implements IParamBuilder<T> {
private T parameter;
public RequiredParamBuilder(T parameter) {
this.parameter = parameter;
}
public RequiredParamBuilder() {
this(null);
}
@Override
public T get() {
return Objects.requireNonNull(parameter);
}
@Override
public void set(T parameter) {
this.parameter = parameter;
}
}

View File

@ -0,0 +1,15 @@
package fr.altarik.toolbox.core.command;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.command.ServerCommandSource;
public abstract class AbstractCommand implements Command {
protected final ServerCommandSource source;
protected final CommandContext<ServerCommandSource> context;
protected AbstractCommand(CommandContext<ServerCommandSource> c) {
this.context = c;
this.source = c.getSource();
}
}

View File

@ -0,0 +1,9 @@
package fr.altarik.toolbox.core.command;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
public interface Command {
int run() throws CommandSyntaxException;
}

View File

@ -0,0 +1,64 @@
package fr.altarik.toolbox.core.config;
import com.google.gson.*;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Code "inspired" from <a href="https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java">
* https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java
* </a>
*/
public class ConfigI {
protected static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).setPrettyPrinting().excludeFieldsWithModifiers(Modifier.PROTECTED, Modifier.PRIVATE).create();
protected Path configPath;
protected static Path getConfigPath(Path configPath, String name) {
return configPath.resolve(name);
}
public static <T extends ConfigI> T load(Path configPath, String name, Class<T> clazz) throws IOException, JsonSyntaxException, JsonIOException {
Path path = getConfigPath(configPath, name);
T file;
if(Files.exists(path)) {
FileReader reader = new FileReader(path.toFile());
file = GSON.fromJson(reader, clazz);
} else {
try {
file = clazz.getConstructor().newInstance();
} catch (InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new IOException(e);
}
}
file.configPath = path;
file.writeChanges();
return file;
}
public void writeChanges() throws IOException {
Path dir = this.configPath.getParent();
if(!Files.exists(dir)) {
Files.createDirectories(dir);
} else if (!Files.isDirectory(dir)) {
throw new IOException("Not a directory: " + dir);
}
// Use a temporary location next to the config's final destination to replace it atomically
// Path tempPath = this.configPath.resolveSibling(this.configPath.getFileName() + ".tmp");
Files.writeString(this.configPath, GSON.toJson(this));
// Files.copy(tempPath, this.configPath, StandardCopyOption.REPLACE_EXISTING);
// Files.delete(tempPath);
// commented because throws an error on windows each time if the file already exist
}
}

View File

@ -0,0 +1,44 @@
package fr.altarik.toolbox.core.data;
import java.util.*;
public class DataTracker {
private final Map<TrackedData, String> trackedData;
public DataTracker() {
this.trackedData = new HashMap<>();
}
public void startTracking(TrackedData data) {
String v = trackedData.get(data);
if(v == null) {
trackedData.put(data, data.defaultValue());
} else {
throw new IllegalArgumentException("Data " + data.name() + " has already been initialized");
}
}
public String getOrDefault(TrackedData data) {
return Objects.requireNonNull(trackedData.get(data));
}
public void set(TrackedData data, String value) {
String v = trackedData.get(data);
if(v != null) {
trackedData.put(data, value);
} else {
throw new IllegalArgumentException("Data " + data.name() + " is not tracked, please initialize it with DataTracker#startTracking(TrackedData, String) first");
}
}
public Iterator<TrackedData> getTrackedDataIterator() {
return trackedData.keySet().iterator();
}
public Iterator<String> getTrackedDataValueIterator() {
return trackedData.values().iterator();
}
}

View File

@ -0,0 +1,5 @@
package fr.altarik.toolbox.core.data;
public record TrackedData(String name, String defaultValue) {
}

View File

@ -0,0 +1,37 @@
package fr.altarik.toolbox.core.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
public interface PlayerLifecycleCallback {
Event<PlayerJoin> PLAYER_JOIN = EventFactory.createArrayBacked(PlayerJoin.class, listeners -> player -> {
for(PlayerJoin listener : listeners) {
ActionResult result = listener.onPlayerJoin(player);
if (result != ActionResult.PASS)
return result;
}
return ActionResult.PASS;
});
Event<PlayerLeave> PLAYER_LEAVE = EventFactory.createArrayBacked(PlayerLeave.class, listeners -> player -> {
for(PlayerLeave listener : listeners) {
ActionResult result = listener.onPlayerLeave(player);
if (result != ActionResult.PASS)
return result;
}
return ActionResult.PASS;
});
@FunctionalInterface
interface PlayerJoin {
ActionResult onPlayerJoin(ServerPlayerEntity player);
}
@FunctionalInterface
interface PlayerLeave {
ActionResult onPlayerLeave(ServerPlayerEntity player);
}
}

View File

@ -0,0 +1,20 @@
package fr.altarik.toolbox.core.mixin;
import fr.altarik.toolbox.core.event.PlayerLifecycleCallback;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ConnectedClientData;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerManager.class)
public class PlayerJoinEvent {
@Inject(method = "onPlayerConnect", at = @At(value = "RETURN"))
private void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
PlayerLifecycleCallback.PLAYER_JOIN.invoker().onPlayerJoin(player);
}
}

View File

@ -0,0 +1,23 @@
package fr.altarik.toolbox.core.mixin;
import fr.altarik.toolbox.core.event.PlayerLifecycleCallback;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayNetworkHandler.class)
public class PlayerLeaveEvent {
@Shadow
public ServerPlayerEntity player;
@Inject(at = @At(value = "HEAD"), method = "onDisconnected")
private void onPlayerQuit(Text reason, CallbackInfo ci) {
PlayerLifecycleCallback.PLAYER_LEAVE.invoker().onPlayerLeave(player);
}
}

View File

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "fr.altarik.toolbox.core.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"PlayerJoinEvent",
"PlayerLeaveEvent"
],
"verbose": false,
"injectors": {
"defaultRequire": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View File

@ -0,0 +1,39 @@
{
"schemaVersion": 1,
"id": "toolbox-core",
"version": "${version}",
"name": "Altarik Toolbox Core",
"description": "Dependency of some of altarik toolbox mods",
"authors": [
"Altarik"
],
"contributors": [
],
"contact": {
"homepage": "https://altarik.fr"
},
"license": "Altarik @ All-Rights-Reserved ",
"icon": "assets/core/icon.png",
"environment": "*",
"entrypoints": {
"main": []
},
"mixins": [
"Core.mixins.json"
],
"depends": {
"fabricloader": "^${loaderVersion}",
"fabric-api": "*",
"minecraft": "${minecraftVersion}",
"java": ">=17"
},
"custom": {
"modmenu": {
"badges": [ "library" ],
"parent": {
"parent": "toolbox"
}
}
}
}

View File

@ -0,0 +1,38 @@
import fr.altarik.toolbox.core.builder.IBuilder;
import fr.altarik.toolbox.core.builder.RequiredCollectionParameterBuilder;
import fr.altarik.toolbox.core.builder.RequiredParamBuilder;
import java.util.ArrayList;
import java.util.List;
public class BuilderImpl implements IBuilder<BuilderResult> {
private final RequiredCollectionParameterBuilder<String, List<String>> collection;
private final RequiredParamBuilder<Integer> numberOfSentences;
private BuilderImpl(boolean canBeEmpty) {
this.collection = new RequiredCollectionParameterBuilder<>(new ArrayList<>(), canBeEmpty);
this.numberOfSentences = new RequiredParamBuilder<>();
}
public BuilderImpl addSentence(String sentence) {
collection.add(sentence);
return this;
}
public BuilderImpl numberOfSentence(int i) {
this.numberOfSentences.set(i);
return this;
}
public static BuilderImpl builder(boolean canBeEmpty) {
return new BuilderImpl(canBeEmpty);
}
@Override
public BuilderResult build() {
return new BuilderResult(collection.get(), numberOfSentences.get());
}
}

View File

@ -0,0 +1,5 @@
import java.util.List;
public record BuilderResult(List<String> sentences, int numberOfSentences) {
}

View File

@ -0,0 +1,32 @@
import fr.altarik.toolbox.core.builder.EmptyCollectionException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
public class BuilderTest {
@Test
void builderTest() throws Exception {
BuilderImpl builder = BuilderImpl.builder(true);
builder.addSentence("First sentence");
builder.addSentence("Second sentence");
builder.numberOfSentence(2);
BuilderResult res = builder.build();
Assertions.assertEquals(Arrays.asList("First sentence", "Second sentence"), res.sentences());
Assertions.assertEquals(res.numberOfSentences(), 2);
BuilderImpl builder1 = BuilderImpl.builder(false);
builder1.numberOfSentence(3);
Assertions.assertThrowsExactly(EmptyCollectionException.class, builder1::build);
BuilderImpl builder2 = BuilderImpl.builder(true);
builder2.numberOfSentence(3);
Assertions.assertDoesNotThrow(builder2::build);
BuilderImpl builder3 = BuilderImpl.builder(true);
Assertions.assertThrowsExactly(NullPointerException.class, builder3::build);
}
}

View File

@ -0,0 +1,40 @@
import fr.altarik.toolbox.core.config.ConfigI;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Path;
public class ConfigITest {
public static class ConfigClazz extends ConfigI {
public int par1 = 5;
public String par2 = "bad";
public double para3 = 3.14;
public static ConfigClazz load() throws IOException {
return load(Path.of("."), "test.json", ConfigClazz.class);
}
}
@Test
public void testConfig() throws IOException {
ConfigClazz config = ConfigClazz.load();
Assertions.assertEquals(5, config.par1);
Assertions.assertEquals("bad", config.par2);
Assertions.assertEquals(3.14, config.para3);
config.par1 = 6;
config.par2 = "good";
config.para3 = 4.2;
Assertions.assertEquals(6, config.par1);
config.writeChanges();
config = ConfigClazz.load();
Assertions.assertEquals(6, config.par1);
Assertions.assertEquals("good", config.par2);
Assertions.assertEquals(4.2, config.para3);
Path.of(".").resolve("test.json").toFile().delete();
}
}

View File

@ -1,21 +1,9 @@
plugins {
id 'java'
id 'maven-publish'
}
dependencies { dependencies {
implementation 'org.postgresql:postgresql:42.5.0' implementation 'org.postgresql:postgresql:42.6.0'
testImplementation 'com.google.code.gson:gson:2.10' testImplementation 'com.google.code.gson:gson:2.10'
testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.0" implementation project(':Core')
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.9.0"
}
java {
withSourcesJar()
withJavadocJar()
} }
test { test {
useJUnitPlatform() exclude 'fr/altarik/toolbox/database/**' // exclude for runner
} }

View File

@ -25,16 +25,11 @@ public abstract class AbstractSqlConnection implements SqlConnection {
return connection; return connection;
} }
public void closeConnection() { @Override
try { public void close() throws Exception {
if(!connection.isClosed()) { if(!connection.isClosed()) {
connection.close(); connection.close();
connection = null; connection = null;
}
} catch(SQLException ignored) {
// no op
} }
} }
} }

View File

@ -1,14 +1,28 @@
package fr.altarik.toolbox.database; package fr.altarik.toolbox.database;
import fr.altarik.toolbox.database.keyValue.KeyValueBuilder;
import fr.altarik.toolbox.database.keyValue.KeyValueTable;
import java.sql.SQLException; import java.sql.SQLException;
public class Connections { public class Connections {
/** /**
* Create a new Connection object for a postgresql database server * Create a new Connection object for a postgresql database server
* @return * @return postgresql connection
*/ */
public static SqlConnection newPostgresConnection(ConnectionConfig config) throws SQLException { public static SqlConnection newPostgresConnection(ConnectionConfig config) throws SQLException {
return new PostgresConnection(config); return new PostgresConnection(config);
} }
/**
* Create a new (key, value) table if not exist and use it through {@link KeyValueTable} interface
* @param connection Postgresql connection
* @param tableName name of the table to use
* @return interface to control the table
* @throws SQLException if connection is lost
*/
public static KeyValueTable newKeyValueTable(SqlConnection connection, String tableName) throws SQLException {
return KeyValueBuilder.builder().setConnection(connection).setTableName(tableName).build();
}
} }

View File

@ -5,7 +5,7 @@ import java.sql.SQLException;
public class PostgresConnection extends AbstractSqlConnection { public class PostgresConnection extends AbstractSqlConnection {
PostgresConnection(ConnectionConfig config) throws SQLException { public PostgresConnection(ConnectionConfig config) throws SQLException {
super(config); super(config);
} }

View File

@ -3,14 +3,24 @@ package fr.altarik.toolbox.database;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
public interface SqlConnection { public interface SqlConnection extends AutoCloseable {
/**
* Start the connection to sql database
* @throws SQLException if unable to connect to database
*/
void connect() throws SQLException; void connect() throws SQLException;
/**
* Get the sql connection
* @return the connection session
*/
Connection getConnection(); Connection getConnection();
/**
* Reconnect you to database if it has closed or lost.
* @throws SQLException if unable to reconnect you
*/
void checkConnection() throws SQLException; void checkConnection() throws SQLException;
void closeConnection();
} }

View File

@ -0,0 +1,39 @@
package fr.altarik.toolbox.database.keyValue;
import fr.altarik.toolbox.core.builder.IBuilder;
import fr.altarik.toolbox.core.builder.RequiredParamBuilder;
import fr.altarik.toolbox.database.SqlConnection;
import org.jetbrains.annotations.NotNull;
import java.sql.SQLException;
public class KeyValueBuilder implements IBuilder<KeyValuePostgresql> {
private final RequiredParamBuilder<String> tableName;
private final RequiredParamBuilder<SqlConnection> connection;
private KeyValueBuilder() {
this.tableName = new RequiredParamBuilder<>();
this.connection = new RequiredParamBuilder<>();
}
public static KeyValueBuilder builder() {
return new KeyValueBuilder();
}
public KeyValueBuilder setConnection(@NotNull SqlConnection connection) {
this.connection.set(connection);
return this;
}
public KeyValueBuilder setTableName(@NotNull String tableName) {
this.tableName.set(tableName);
return this;
}
public KeyValuePostgresql build() throws SQLException {
return new KeyValuePostgresql(connection.get(), tableName.get());
}
}

View File

@ -0,0 +1,78 @@
package fr.altarik.toolbox.database.keyValue;
import fr.altarik.toolbox.database.SqlConnection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class KeyValuePostgresql implements KeyValueTable {
private final SqlConnection connection;
private final String tableName;
public KeyValuePostgresql(@NotNull SqlConnection connection, @NotNull String tableName) throws SQLException {
this.connection = connection;
this.tableName = tableName;
connection.checkConnection();
createTable(tableName);
}
private void createTable(String tableName) throws SQLException {
try(Statement statement = connection.getConnection().createStatement()) {
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + tableName + "(key VARCHAR(50) NOT NULL, value TEXT NOT NULL, PRIMARY KEY(key));");
}
}
@Override
public @Nullable String getValue(String key) throws SQLException {
connection.checkConnection();
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("SELECT value FROM " + tableName + " WHERE key=?;")) {
preparedStatement.setString(1, key);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.next())
return resultSet.getString(1);
return null;
}
}
@Override
public void insertValue(String key, String value) throws SQLException {
connection.checkConnection();
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("INSERT INTO " + tableName + "(key, value) VALUES (?, ?);")){
preparedStatement.setString(1, key);
preparedStatement.setString(2, value);
preparedStatement.executeUpdate();
}
}
@Override
public void updateValue(String key, String value) throws SQLException {
connection.checkConnection();
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("UPDATE " + tableName + " SET value=? WHERE key=?;")) {
preparedStatement.setString(1, value);
preparedStatement.setString(2, key);
preparedStatement.executeUpdate();
}
}
@Override
public void deleteRow(String key) throws SQLException {
connection.checkConnection();
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("DELETE FROM " + tableName + " WHERE key=?")) {
preparedStatement.setString(1, key);
preparedStatement.executeUpdate();
}
}
@Override
public void truncateTable() throws SQLException {
connection.checkConnection();
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("TRUNCATE TABLE " + tableName)) {
preparedStatement.executeUpdate();
}
}
}

View File

@ -0,0 +1,54 @@
package fr.altarik.toolbox.database.keyValue;
import org.jetbrains.annotations.Nullable;
import java.sql.SQLException;
/**
* <p>Implement of a key value table, abstract the actual representation of the table and its manipulation between this interface</p>
* @see KeyValuePostgresql
*/
public interface KeyValueTable {
/**
* <p>Return the first value associated with the unique key.</p>
*
* @param key String key of associated to the value
* @return value associated with the key
* @throws SQLException if connection is lost
*/
@Nullable String getValue(String key) throws SQLException;
/**
* <p>Insert a new value in the table, associated with key</p>
*
* @param key String key which will be associated with the value, is unique
* @param value String value which will be stored in the database
* @throws SQLException if connection is lost or if {@code key} is not unique (already exist in database)
*/
void insertValue(String key, String value) throws SQLException;
/**
* <p>Update value column of the row associated with the key by {@code value}</p>
* <p>If {@code key} doesn't exist in table, will update no row without warning</p>
* @param key String key which will is associated with the value, is unique
* @param value new value
* @throws SQLException if connection is lost
*/
void updateValue(String key, String value) throws SQLException;
/**
* <p>Delete row with having {@code key} as unique key</p>
* <p>If key doesn't exist in database, will delete no row without warning</p>
* @param key the key of the row to delete
* @throws SQLException if connection is lost
*/
void deleteRow(String key) throws SQLException;
/**
* Will delete every data inside the table
* @throws SQLException if connection is lost
*/
void truncateTable() throws SQLException;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View File

@ -0,0 +1,38 @@
{
"schemaVersion": 1,
"id": "toolbox-database",
"version": "${version}",
"name": "Altarik Toolbox Database",
"description": "A set of sql tools",
"authors": [
"Altarik"
],
"contributors": [
],
"contact": {
"homepage": "https://altarik.fr"
},
"license": "Altarik @ All-Rights-Reserved ",
"icon": "assets/database/icon.png",
"environment": "*",
"entrypoints": {
"main": []
},
"mixins": [],
"depends": {
"fabricloader": "^${loaderVersion}",
"fabric-api": "*",
"minecraft": "${minecraftVersion}",
"java": ">=17",
"toolbox-core": "${version}"
},
"custom": {
"modmenu": {
"badges": [ "library" ],
"parent": {
"parent": "toolbox"
}
}
}
}

View File

@ -30,6 +30,7 @@ class ConnectionTest {
try(PreparedStatement statement = connection.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS toolbox(id SERIAL, PRIMARY KEY (id));")) { try(PreparedStatement statement = connection.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS toolbox(id SERIAL, PRIMARY KEY (id));")) {
statement.executeUpdate(); statement.executeUpdate();
} }
connection.close();
}); });
} }

View File

@ -0,0 +1,68 @@
package fr.altarik.toolbox.database.keyValue;
import com.google.gson.Gson;
import fr.altarik.toolbox.database.ConnectionConfig;
import fr.altarik.toolbox.database.Connections;
import fr.altarik.toolbox.database.SqlConnection;
import org.junit.jupiter.api.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
public class KeyValueTest {
@Test
void tableTest() {
System.out.println("Hello");
assertDoesNotThrow(() -> {InputStream configInput = getResource("config.yml");
String configStr = new BufferedReader(new InputStreamReader(Objects.requireNonNull(configInput)))
.lines().collect(Collectors.joining("\n"));
Gson gson = new Gson();
ConnectionConfig config = gson.fromJson(configStr, ConnectionConfig.class);
try(SqlConnection connection = Connections.newPostgresConnection(config)) {
KeyValueTable keyValueTable = Connections.newKeyValueTable(connection, "toolbox_keyvalue");
keyValueTable.truncateTable();
keyValueTable.insertValue("location", "here");
keyValueTable.insertValue("experience", "5");
assertEquals("here", keyValueTable.getValue("location"));
assertEquals("5", keyValueTable.getValue("experience"));
keyValueTable.updateValue("location", "Elsewhere");
assertEquals("Elsewhere", keyValueTable.getValue("location"));
assertEquals("5", keyValueTable.getValue("experience"));
keyValueTable.updateValue("experience", "10");
assertEquals("Elsewhere", keyValueTable.getValue("location"));
assertEquals("10", keyValueTable.getValue("experience"));
keyValueTable.deleteRow("experience");
assertEquals("Elsewhere", keyValueTable.getValue("location"));
assertNull(keyValueTable.getValue("experience"));
keyValueTable.truncateTable();
assertNull(keyValueTable.getValue("location"));
assertNull(keyValueTable.getValue("experience"));
}
});
}
// TODO: 08/06/2023 Move to Core module in a toolkit class
private InputStream getResource(String resourcePath) {
try {
URL url = this.getClass().getClassLoader().getResource(resourcePath);
if(url == null)
return null;
URLConnection connection = url.openConnection();
connection.setUseCaches(false);
return connection.getInputStream();
} catch (IOException e){
return null;
}
}
}

View File

@ -3,5 +3,5 @@
"port": 5432, "port": 5432,
"database": "postgres", "database": "postgres",
"username": "postgres", "username": "postgres",
"password": "root" "password": "Vaubadon1"
} }

View File

@ -0,0 +1,113 @@
package fr.altarik.toolbox.pagination;
import fr.altarik.toolbox.pagination.api.PageIndexOutOfBoundException;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class PaginatedContent {
private final List<Page> pages;
private final Text header;
public PaginatedContent(String header, String content) {
this.header = buildHeader(header);
pages = new ArrayList<>();
List<String> secondSplit = new ArrayList<>();
for(String elem : Stream.of(content.split("\n")).collect(Collectors.toCollection(ArrayList::new))) {
if(elem.length() > 50) {
secondSplit.add(elem.substring(0, 50));
secondSplit.add(elem.substring(51, elem.length() - 1));
} else {
secondSplit.add(elem);
}
}
List<Text> currentPage = new ArrayList<>();
for(String elem : secondSplit) {
if(!elem.isEmpty()) {
currentPage.add(Text.literal(elem));
}
if(currentPage.size() == 8 || elem.isEmpty()) {
pages.add(new Page(currentPage));
currentPage = new ArrayList<>();
}
}
pages.add(new Page(currentPage));
}
public PaginatedContent(@Nullable Text header, List<Text> content) {
this.header = buildHeader(header);
this.pages = new ArrayList<>();
List<Text> currentPage = new ArrayList<>();
for(Text elem : content) {
if(elem != null)
currentPage.add(elem);
if(currentPage.size() == 8 || elem == null) {
pages.add(new Page(currentPage));
currentPage = new ArrayList<>();
}
}
pages.add(new Page(currentPage));
}
private Text buildHeader(@Nullable String header) {
int numberOfEq = (50 - (header != null ? header.length() : 0)) / 2;
return Text.literal("=".repeat(numberOfEq) + " " + header + " " + "=".repeat(numberOfEq));
}
private Text buildHeader(@Nullable Text header) {
int numberOfEq = (50 - (header != null ? header.getString().length() : 0)) / 2;
return Text.literal("=".repeat(numberOfEq) + " ").append(header).append(" " + "=".repeat(numberOfEq));
}
public void display(ServerPlayerEntity playerEntity, int page) throws PageIndexOutOfBoundException {
if(page >= this.pages.size()) {
throw new PageIndexOutOfBoundException("api.pagination.page_higher_than_expected", this.pages.size(), (page + 1));
} else if(page < 0) {
throw new PageIndexOutOfBoundException("api.pagination.page_lower_than_0");
} else {
playerEntity.sendMessage(header);
for(Text s : pages.get(page).lines) {
playerEntity.sendMessage(s);
}
playerEntity.sendMessage(buildFooter(page));
}
}
private Text buildFooter(int page) {
String strPage = String.valueOf(page + 1);
int numberOfEq = (46 - strPage.length()) / 2;
MutableText left = Text.literal(" <").styled(
style -> style
.withColor(Formatting.YELLOW)
.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/table page " + (page - 1)))
);
MutableText middle = Text.literal(" " + strPage + " ").styled(style -> style.withColor(Formatting.RESET));
MutableText right = Text.literal("> ").styled(
style -> style
.withColor(Formatting.YELLOW)
.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/table page " + (page + 1)))
);
return Text.literal("=".repeat(numberOfEq))
.append(left)
.append(middle)
.append(right)
.append(
Text.literal("=".repeat(numberOfEq))
.styled(style -> style.withColor(Formatting.RESET))
);
}
private record Page(List<Text> lines) {
}
}

View File

@ -0,0 +1,33 @@
package fr.altarik.toolbox.pagination;
import fr.altarik.toolbox.pagination.api.PaginationApi;
import fr.altarik.toolbox.pagination.api.PaginationApiImpl;
import fr.altarik.toolbox.pagination.command.CommandsRegister;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import org.jetbrains.annotations.NotNull;
public class Pagination implements ModInitializer {
private static Pagination instance;
private final PaginationApi api;
public Pagination() {
instance = this;
this.api = new PaginationApiImpl();
}
@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> new CommandsRegister(this).register(dispatcher));
}
public @NotNull PaginationApi getApi() {
return api;
}
@SuppressWarnings("unused")
public static @NotNull Pagination getInstance() {
return instance;
}
}

View File

@ -0,0 +1,20 @@
package fr.altarik.toolbox.pagination.api;
import net.minecraft.text.Text;
public class PageIndexOutOfBoundException extends Exception {
private final Text text;
public PageIndexOutOfBoundException(String s) {
this.text = Text.translatable(s);
}
public PageIndexOutOfBoundException(String s, int size, int currentPage) {
this.text = Text.translatable(s, size, currentPage);
}
public Text getText() {
return text;
}
}

View File

@ -0,0 +1,65 @@
package fr.altarik.toolbox.pagination.api;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public interface PaginationApi {
/**
* <p>Create a pagination table for player, content is separated into multiple pages.<br>
* You can separate yourself content between two pages by adding *\n\n*.</p>
* <p>Content have a time-to-live of 15 minutes (18,000 ticks)</p>
* @param playerEntity The player who will be able to interact and see the paginated message
* @param content Content you want to paginate
* @param header Header/title you want to add to every page, empty space is filled with "=".
* <p>Special values are:
* <ul><li><b>null</b> if you doesn't want to add a header</li>
* <li><b>empty String</b> if you want just the header to be filled only with "="</li></ul>
* @param display true if you want the message to be displayed now, false otherwise if you want to display the
* message yourself
* @throws IllegalArgumentException if one of its conditions is met: <ol>
* <li><b>header</b> length is more than 50 characters</li>
* <li><b>content</b> is empty/blank</li>
* <li><b>playerEntity</b> or <b>content</b> are null</li>
* </ol>
*/
void createTable(ServerPlayerEntity playerEntity, String content, String header, boolean display);
/**
* <p>Create a pagination table for player the same way than
* {@link PaginationApi#createTable(ServerPlayerEntity, String, String, boolean)},
* content is separated into multiple pages.<br />
* You can separate yourself content between 2 pages by adding a null instance of Text in content list.</p>
* <p>Content have a time-to-live of 15 minutes (18,000 ticks)</p>
* @param playerEntity The player who will be able to interact and see the paginated message
* @param content Content you want to paginate
* @param header header/title you want to add to every page, empty space is filled with "=".
* <p>Special values are:</p>
* <ul><li><b>null</b> if you doesn't want to add a header</li>
* <li><b>Empty text</b>if you want just the header to be filled only with "="</li></ul>
* @param display true if you want the message to be displayed now, false otherwise if you want to display the
* message yourself
* @throws IllegalArgumentException if one of its conditions is met: <ol>
* <li><b>header</b> length is more than 50 characters</li>
* <li><b>content</b> is empty/blank</li>
* <li><b>playerEntity</b> or <b>content</b> are null</li>
* </ol>
* @see Text#empty()
*/
void createTable(ServerPlayerEntity playerEntity, List<Text> content, @Nullable Text header, boolean display);
/**
* Display the given page for the given player
* @param player display the content of this player
* @param page display this page
* @throws IllegalArgumentException if page is invalid
* @throws NullPointerException if player is null or paginated content for the player doesn't exist (or have expired)
* @see fr.altarik.toolbox.pagination.PaginatedContent#display(ServerPlayerEntity, int)
*/
void display(ServerPlayerEntity player, int page) throws PageIndexOutOfBoundException;
}

View File

@ -0,0 +1,85 @@
package fr.altarik.toolbox.pagination.api;
import fr.altarik.toolbox.pagination.PaginatedContent;
import fr.altarik.toolbox.pagination.precondition.ContentCondition;
import fr.altarik.toolbox.pagination.precondition.HeaderCondition;
import fr.altarik.toolbox.pagination.precondition.NullPlayerCondition;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Pair;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class PaginationApiImpl implements PaginationApi {
/**
* Integer represent relative tts of the paginated content, decreased by 1 every seconds
*/
public final Map<ServerPlayerEntity, Pair<Integer, PaginatedContent>> paginatedContent = new HashMap<>();
private final Predicate<ServerPlayerEntity> playerCondition = new NullPlayerCondition().negate();
private final Predicate<String> headerCondition = new HeaderCondition().negate();
private final Predicate<String> contentCondition = new ContentCondition().negate();
public PaginationApiImpl() {
ServerTickEvents.START_SERVER_TICK.register(this::serverTick);
}
@Override
public void createTable(ServerPlayerEntity playerEntity, String content, String header, boolean display) {
if(playerCondition.test(playerEntity) || headerCondition.test(header) || contentCondition.test(content)) {
throw new IllegalArgumentException("Preconditions aren't satisfied");
}
PaginatedContent paginatedContent1 = new PaginatedContent(header, content);
storeAndDisplay(playerEntity, paginatedContent1, display);
}
@Override
public void createTable(ServerPlayerEntity playerEntity, List<Text> content, @Nullable Text header, boolean display) {
if(playerCondition.test(playerEntity)) {
throw new IllegalArgumentException("Preconditions aren't satisfied");
}
PaginatedContent paginatedContent1 = new PaginatedContent(header, content);
storeAndDisplay(playerEntity, paginatedContent1, display);
}
private void storeAndDisplay(ServerPlayerEntity playerEntity, PaginatedContent paginatedContent1, boolean display) {
paginatedContent.put(playerEntity, new Pair<>(18000, paginatedContent1));
if(display) {
try {
paginatedContent1.display(playerEntity, 0);
} catch (PageIndexOutOfBoundException e) {
throw new IllegalArgumentException(e);
}
}
}
@Override
public void display(ServerPlayerEntity player, int page) throws PageIndexOutOfBoundException {
if(player == null)
throw new NullPointerException("Player is null");
Pair<Integer, PaginatedContent> pair = paginatedContent.get(player);
if(pair == null)
throw new NullPointerException("No paginated page for player " + player.getCustomName());
pair.getRight().display(player, page);
}
private void serverTick(MinecraftServer server) {
List<ServerPlayerEntity> toRemove = new ArrayList<>();
for(Map.Entry<ServerPlayerEntity, Pair<Integer, PaginatedContent>> content : paginatedContent.entrySet()) {
if(content.getValue().getLeft() == 0) {
toRemove.add(content.getKey());
} else {
content.getValue().setLeft(content.getValue().getLeft() - 1);
}
}
for(ServerPlayerEntity player : toRemove) {
paginatedContent.remove(player);
}
}
}

View File

@ -0,0 +1,102 @@
package fr.altarik.toolbox.pagination.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import fr.altarik.toolbox.pagination.Pagination;
import fr.altarik.toolbox.pagination.api.PageIndexOutOfBoundException;
import fr.altarik.toolbox.pagination.api.PaginationApi;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import java.util.ArrayList;
import java.util.List;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class CommandsRegister {
private final PaginationApi api;
public CommandsRegister(Pagination instance) {
this.api = instance.getApi();
}
public void register(CommandDispatcher<ServerCommandSource> dispatcher) {
dispatcher.register(literal("table")
.then(literal("page")
.then(argument("page", IntegerArgumentType.integer())
.executes(this::selectPageCommand)
)
).then(literal("test")
.requires(source -> source.isExecutedByPlayer() && source.hasPermissionLevel(3))
.executes(this::testPageCommand)
).then(literal("testText")
.requires(source -> source.isExecutedByPlayer() && source.hasPermissionLevel(3))
.executes(this::testPageTextCommand)
)
);
}
/**
* Simply a debug command
*/
private int testPageCommand(CommandContext<ServerCommandSource> context) {
api.createTable(context.getSource().getPlayer(), """
first line, string version
Second line
second page
dqdq
dqdqd
qdqdq
dqdq
dqdq
dqdq
dqdqd
third page
dqdqd
dqdqd
d""", "My header", true);
return 0;
}
private int testPageTextCommand(CommandContext<ServerCommandSource> context) {
List<Text> content = new ArrayList<>();
content.add(Text.literal("first line, text version"));
content.add(Text.literal("Second line"));
content.add(null);
content.add(Text.literal("second page"));
content.add(Text.literal("dqdq"));
content.add(Text.literal("dqdqd"));
content.add(Text.literal("dqdqd"));
content.add(Text.literal("dqdq"));
content.add(Text.literal("dqdq"));
content.add(Text.literal("dqdq"));
content.add(Text.literal("dqdqd"));
content.add(Text.literal("third page"));
content.add(Text.literal("dqdqd"));
content.add(Text.literal("dqdqd"));
api.createTable(context.getSource().getPlayer(), content, Text.literal("My Text Header"), true);
return 0;
}
private int selectPageCommand(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
try {
int page = IntegerArgumentType.getInteger(context, "page");
api.display(context.getSource().getPlayerOrThrow(), page);
} catch(PageIndexOutOfBoundException e) {
throw new CommandSyntaxException(new SimpleCommandExceptionType(e.getText()), e.getText());
}
return 0;
}
private enum TestType {
String,
Text;
}
}

View File

@ -0,0 +1,15 @@
package fr.altarik.toolbox.pagination.precondition;
import java.util.function.Predicate;
/**
* This predicate returns true if the String is not null or
* if its content is not blank (empty or only contains whitespaces)
*/
public class ContentCondition implements Predicate<String> {
@Override
public boolean test(String s) {
return s != null && !s.isBlank();
}
}

View File

@ -0,0 +1,13 @@
package fr.altarik.toolbox.pagination.precondition;
import java.util.function.Predicate;
/**
* This predicate returns true if its length doesn't exceed 50 characters.
*/
public class HeaderCondition implements Predicate<String> {
@Override
public boolean test(String header) {
return header.length() <= 50;
}
}

View File

@ -0,0 +1,15 @@
package fr.altarik.toolbox.pagination.precondition;
import net.minecraft.server.network.ServerPlayerEntity;
import java.util.function.Predicate;
/**
* This predicate returns true if the player isn't null, false otherwise
*/
public class NullPlayerCondition implements Predicate<ServerPlayerEntity> {
@Override
public boolean test(ServerPlayerEntity player) {
return player != null;
}
}

View File

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "fr.altarik.toolbox.task.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
],
"client": [
],
"verbose": false,
"injectors": {
"defaultRequire": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View File

@ -0,0 +1,4 @@
{
"api.pagination.page_lower_than_0": "argument page is lower than 0",
"api.pagination.page_higher_than_expected": "There's %d paginated pages but you wanted page n°%d"
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"id": "toolbox-pagination",
"version": "${version}",
"name": "Altarik Toolbox Pagination",
"description": "A mod to use to paginate long result to player in chat",
"authors": [
"Altarik"
],
"contributors": [
],
"contact": {
"homepage": "https://altarik.fr"
},
"license": "Altarik @ All-Rights-Reserved ",
"icon": "assets/pagination/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"fr.altarik.toolbox.pagination.Pagination"
]
},
"mixins": [
"Pagination.mixins.json"
],
"depends": {
"fabricloader": "^${loaderVersion}",
"fabric-api": "*",
"minecraft": "${minecraftVersion}",
"java": ">=17"
},
"custom": {
"modmenu": {
"badges": [ "library" ],
"parent": {
"parent": "toolbox"
}
}
}
}

View File

@ -1,18 +0,0 @@
plugins {
id 'java'
id 'maven-publish'
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}"
}
java {
withSourcesJar()
withJavadocJar()
}
test {
useJUnitPlatform()
}

View File

@ -0,0 +1,18 @@
package fr.altarik.toolbox.task;
public abstract class AltarikRunnable implements Runnable {
private boolean isCancelled = false;
/**
* Warning: Some task cannot be cancelled (mostly async tasks like {@link fr.altarik.toolbox.task.async.AsyncTasks})
* The result of this call is ignored in this case, you can still add a way to not execute its content (like if(isCancelled) return;)
*/
public void cancel() {
this.isCancelled = true;
}
public boolean isCancelled() {
return isCancelled;
}
}

View File

@ -0,0 +1,14 @@
package fr.altarik.toolbox.task;
public interface PeriodicTaskI extends TaskI {
/**
* Run a task periodically
* @param function the function to execute
* @param delay delay before starting the task
* @param period time to wait between runs
* @throws InterruptedException When executed asynchronously, task may be interrupted
* @see fr.altarik.toolbox.task.sync.PeriodicSyncTask
*/
void addTask(AltarikRunnable function, long delay, long period) throws InterruptedException;
}

View File

@ -0,0 +1,41 @@
package fr.altarik.toolbox.task;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public class Scheduler implements Runnable {
private final Queue<SchedulerTaskData> tasks;
private final SendTaskWorkerI worker;
public Scheduler(SendTaskWorkerI worker, Queue<SchedulerTaskData> tasks) {
this.worker = worker;
this.tasks = tasks;
}
@Override
public void run() {
List<SchedulerTaskData> removeList = new ArrayList<>(tasks.size());
for(SchedulerTaskData data : tasks) {
if(!data.getFunction().isCancelled()) {
long currentDelay = data.getCurrentDelay();
if(currentDelay != 0) {
data.setCurrentDelay(currentDelay - 1);
} else {
worker.sendTask(data.getFunction());
if(data.getPeriod() == -1) {
removeList.add(data);
} else {
data.setCurrentDelay(data.getPeriod());
}
}
} else {
removeList.add(data);
}
}
for(SchedulerTaskData toRemove : removeList) {
tasks.remove(toRemove);
}
}
}

View File

@ -0,0 +1,55 @@
package fr.altarik.toolbox.task;
public class SchedulerTaskData {
/**
* Delay before executing the function for the first time
* Correspond to tick in synchronous context and milliseconds in asynchronous context
*/
private final long delay;
/**
* Period of time before re-executing the function
* Correspond to tick in synchronous context and milliseconds in asynchronous context
*/
private final long period;
private final AltarikRunnable function;
private long currentDelay;
/**
*
* Delay and Period times corresponds to tick in synchronous context and milliseconds in asynchronous context
*
* @param function instructions to execute
* @param delay Delay before executing the function for the first time
* @param period Period of time before re-executing the function
*/
public SchedulerTaskData(AltarikRunnable function, long delay, long period) {
this.function = function;
this.delay = delay;
this.period = period;
this.currentDelay = delay;
}
public AltarikRunnable getFunction() {
return function;
}
public long getCurrentDelay() {
return currentDelay;
}
public void setCurrentDelay(long currentDelay) {
this.currentDelay = currentDelay;
}
public long getDelay() {
return delay;
}
public long getPeriod() {
return period;
}
}

View File

@ -0,0 +1,11 @@
package fr.altarik.toolbox.task;
public interface SendTaskWorkerI {
/**
* Internal use for scheduler, do not use.
* Scheduler use this method to send the task to execute to worker
* @param task task to execute now
*/
void sendTask(AltarikRunnable task);
}

View File

@ -0,0 +1,19 @@
package fr.altarik.toolbox.task;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.server.MinecraftServer;
public class ServerTickListener {
private final Runnable task;
public ServerTickListener(Runnable syncTask) {
this.task = syncTask;
ServerTickEvents.START_SERVER_TICK.register(this::onServerTick);
}
private void onServerTick(MinecraftServer minecraftServer) {
task.run();
}
}

View File

@ -0,0 +1,12 @@
package fr.altarik.toolbox.task;
public interface TaskI {
/**
* Send task to worker, execution depends on implementation
* @param function task you send to worker
* @throws InterruptedException used by asynchronous workers if threads has been interrupted or shutdown
*/
void addTask(AltarikRunnable function) throws InterruptedException;
}

View File

@ -0,0 +1,92 @@
package fr.altarik.toolbox.task.async;
import fr.altarik.toolbox.task.*;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* A task manager to execute periodic tasks asynchronously. A scheduler on the main/server thread will send the task to
* worker threads.
*/
public class AsyncPeriodicTasks implements PeriodicTaskI, AsyncTaskI, SendTaskWorkerI {
private final ExecutorService worker;
private final Queue<SchedulerTaskData> tasks;
protected final Scheduler scheduler;
private final ServerTickListener listener;
private AsyncPeriodicTasks(int numberOfWorker) {
if(numberOfWorker == 1) {
worker = Executors.newSingleThreadExecutor();
} else if (numberOfWorker <= 0) {
worker = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
} else {
worker = Executors.newFixedThreadPool(numberOfWorker);
}
tasks = new ConcurrentLinkedQueue<>();
this.scheduler = new Scheduler(this, tasks);
this.listener = new ServerTickListener(scheduler);
}
/**
* Call this method at startup or before first use of {@link AsyncTasks#addTask(AltarikRunnable)}, cause without it, nothing will work
* This method declare worker thread and start it, without call it, by calling addTask(Runnable), it'll add your task to Queue, but tasks will never be consumed.
*
* @return an instance of AsyncTasks
*/
public static PeriodicTaskI initialize(int numberOfWorker) {
return new AsyncPeriodicTasks(numberOfWorker);
}
public static PeriodicTaskI initialize() {
return initialize(Runtime.getRuntime().availableProcessors());
}
/**
* Send the task to the scheduler, the task is executed at the next server tick and at every following tick
* @param function the function which will be executed
* @throws InterruptedException if worker has terminated or is shutting down
*/
@Override
public void addTask(AltarikRunnable function) throws InterruptedException {
this.addTask(function, 0, 1);
}
/**
* Send the task to the scheduler, executed depending on the parameters (delay and period)
* @param function the function to execute
* @param delay delay in tick before starting the task
* @param period time in tick to wait between runs
* @throws InterruptedException if worker has terminated or is shutting down
*/
@Override
public void addTask(AltarikRunnable function, long delay, long period) throws InterruptedException {
if(worker.isTerminated() || worker.isShutdown()) {
throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task");
}
tasks.add(new SchedulerTaskData(function, delay, period - 1));
}
/**
* Try to execute task you already send in 10 seconds, otherwise workers are killed.
* @throws AsyncTasks.UnfinishedTasksException if workers has been shutdown before finishing every tasks
*/
@Override
public void close() throws Exception {
worker.shutdown();
boolean result = worker.awaitTermination(10, TimeUnit.SECONDS);
if(!result) {
worker.shutdownNow();
throw new AsyncTasks.UnfinishedTasksException("Tasks take too many time to finish, shutdown has been enforce");
}
}
@Override
public void sendTask(AltarikRunnable task) {
worker.submit(task);
}
}

View File

@ -0,0 +1,6 @@
package fr.altarik.toolbox.task.async;
import fr.altarik.toolbox.task.TaskI;
public interface AsyncTaskI extends TaskI, AutoCloseable {
}

View File

@ -1,4 +1,6 @@
package fr.altarik.toolbox.asynctasks; package fr.altarik.toolbox.task.async;
import fr.altarik.toolbox.task.AltarikRunnable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -7,7 +9,7 @@ import java.util.concurrent.TimeUnit;
/** /**
* A non-blocking small sized time-consuming tasks to executed asynchronously. * A non-blocking small sized time-consuming tasks to executed asynchronously.
*/ */
public class AsyncTasks implements AutoCloseable { public class AsyncTasks implements AsyncTaskI {
private final ExecutorService worker; private final ExecutorService worker;
@ -15,23 +17,23 @@ public class AsyncTasks implements AutoCloseable {
if(numberOfWorker == 1) { if(numberOfWorker == 1) {
worker = Executors.newSingleThreadExecutor(); worker = Executors.newSingleThreadExecutor();
} else if (numberOfWorker <= 0) { } else if (numberOfWorker <= 0) {
worker = Executors.newCachedThreadPool(); worker = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
} else { } else {
worker = Executors.newFixedThreadPool(numberOfWorker); worker = Executors.newFixedThreadPool(numberOfWorker);
} }
} }
/** /**
* Call this method at startup or before first use of {@link AsyncTasks#addTask(Runnable)}, cause without it, nothing will work * Call this method at startup or before first use of {@link AsyncTasks#addTask(AltarikRunnable)}, cause without it, nothing will work
* This method declare worker thread and start it, without call it, by calling addTask(Runnable), it'll add your task to Queue, but tasks will never be consumed. * This method declare worker thread and start it, without call it, by calling addTask(Runnable), it'll add your task to Queue, but tasks will never be consumed.
* *
* @return an instance of AsyncTasks * @return an instance of AsyncTasks
*/ */
public static AsyncTasks initialize(int numberOfWorker) { public static AsyncTaskI initialize(int numberOfWorker) {
return new AsyncTasks(numberOfWorker); return new AsyncTasks(numberOfWorker);
} }
public static AsyncTasks initialize() { public static AsyncTaskI initialize() {
return initialize(Runtime.getRuntime().availableProcessors()); return initialize(Runtime.getRuntime().availableProcessors());
} }
@ -65,7 +67,7 @@ public class AsyncTasks implements AutoCloseable {
* @param function task to be executed * @param function task to be executed
* @throws InterruptedException when worker thread or BlockQueue has been interrupted while waiting (which is anormal) * @throws InterruptedException when worker thread or BlockQueue has been interrupted while waiting (which is anormal)
*/ */
public void addTask(Runnable function) throws InterruptedException { public void addTask(AltarikRunnable function) throws InterruptedException {
if(worker.isTerminated() || worker.isShutdown()) { if(worker.isTerminated() || worker.isShutdown()) {
throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task"); throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task");
} }
@ -75,9 +77,11 @@ public class AsyncTasks implements AutoCloseable {
/** /**
* This method is call when you want to close workers and wait for waiting tasks to finish * This method is call when you want to close workers and wait for waiting tasks to finish
* *
* @throws UnfinishedTasksException when all tasks cannot be terminated in 10 seconds
* @throws InterruptedException if interrupted while waiting for tasks to finish
*/ */
@Override @Override
public void close() throws Exception { public void close() throws UnfinishedTasksException, InterruptedException {
worker.shutdown(); worker.shutdown();
boolean result = worker.awaitTermination(10, TimeUnit.SECONDS); boolean result = worker.awaitTermination(10, TimeUnit.SECONDS);
if(!result) { if(!result) {

View File

@ -0,0 +1,58 @@
package fr.altarik.toolbox.task.server;
import fr.altarik.toolbox.task.PeriodicTaskI;
import fr.altarik.toolbox.task.TaskI;
import fr.altarik.toolbox.task.async.AsyncPeriodicTasks;
import fr.altarik.toolbox.task.async.AsyncTaskI;
import fr.altarik.toolbox.task.async.AsyncTasks;
import fr.altarik.toolbox.task.sync.PeriodicSyncTask;
import net.fabricmc.api.DedicatedServerModInitializer;
public class DedicatedServerTask implements DedicatedServerModInitializer {
@SuppressWarnings("unused")
public final TaskI asyncWorkers = AsyncTasks.initialize();
@SuppressWarnings("unused")
public final PeriodicTaskI periodicSyncTask = PeriodicSyncTask.initialize();
@SuppressWarnings("unused")
public final AsyncTaskI asyncTasks = AsyncTasks.initialize();
@SuppressWarnings("unused")
public final PeriodicTaskI periodicAsyncTask = AsyncPeriodicTasks.initialize();
@Override
public void onInitializeServer() {
/* try {
asyncWorkers.addTask(new AltarikRunnable() {
@Override
public void run() {
System.out.println("Hello world 1");
}
});
periodicSyncTask.addTask(new AltarikRunnable() {
@Override
public void run() {
System.out.println("Hello world 2");
}
}, 40, 60);
asyncTasks.addTask(new AltarikRunnable() {
@Override
public void run() {
System.out.println("Hello world 3 : " + Thread.currentThread().getName());
}
});
periodicAsyncTask.addTask(new AltarikRunnable() {
@Override
public void run() {
System.out.println("Hello world 4 : " + Thread.currentThread().getName());
}
}, 60, 80);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} */
}
@SuppressWarnings("unused")
public TaskI getAsyncWorkers() {
return asyncWorkers;
}
}

View File

@ -0,0 +1,39 @@
package fr.altarik.toolbox.task.sync;
import fr.altarik.toolbox.task.*;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class PeriodicSyncTask implements PeriodicTaskI, SendTaskWorkerI {
private final ServerTickListener listener;
private final Queue<SchedulerTaskData> tasks;
protected final Scheduler scheduler;
private PeriodicSyncTask() {
this.tasks = new ConcurrentLinkedQueue<>();
this.scheduler = new Scheduler(this, tasks);
this.listener = new ServerTickListener(scheduler);
}
public static PeriodicTaskI initialize() {
return new PeriodicSyncTask();
}
@Override
public void addTask(AltarikRunnable function) {
addTask(function, 0, 1);
}
@Override
public void addTask(AltarikRunnable function, long delay, long period) {
tasks.add(new SchedulerTaskData(function, delay, period - 1));
}
@Override
public void sendTask(AltarikRunnable task) {
task.run();
}
}

View File

@ -0,0 +1,37 @@
package fr.altarik.toolbox.task.sync;
import fr.altarik.toolbox.task.*;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class SyncTask implements TaskI, SendTaskWorkerI {
private final ServerTickListener listener;
private final Queue<SchedulerTaskData> tasks;
protected final Scheduler scheduler;
private SyncTask() {
this.tasks = new ConcurrentLinkedQueue<>();
this.scheduler = new Scheduler(this, tasks);
this.listener = new ServerTickListener(scheduler);
}
public static TaskI initialize() {
return new SyncTask();
}
@Override
public void addTask(AltarikRunnable function) {
addTask(function, 0);
}
public void addTask(AltarikRunnable function, int delay) {
tasks.add(new SchedulerTaskData(function, delay, -1));
}
@Override
public void sendTask(AltarikRunnable task) {
task.run();
}
}

View File

@ -0,0 +1,15 @@
{
"required": true,
"minVersion": "0.8",
"package": "fr.altarik.toolbox.task.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
],
"client": [
],
"verbose": false,
"injectors": {
"defaultRequire": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View File

@ -0,0 +1,34 @@
{
"schemaVersion": 1,
"id": "toolbox-task",
"version": "${version}",
"name": "Altarik Toolbox Task",
"description": "A mod to use as a dependency for others to schedule tasks",
"authors": [
"Altarik"
],
"contributors": [
],
"contact": {
"homepage": "https://altarik.fr"
},
"license": "Altarik @ All-Rights-Reserved ",
"icon": "assets/tasks/icon.png",
"environment": "*",
"entrypoints": {
"server": [
"fr.altarik.toolbox.task.server.DedicatedServerTask"
]
},
"mixins": [
"Task.mixins.json"
],
"depends": {
"fabricloader": "^${loaderVersion}",
"fabric-api": "*",
"minecraft": "${minecraftVersion}",
"java": ">=17"
}
}

View File

@ -1,6 +1,6 @@
package fr.altarik.toolbox; package fr.altarik.toolbox.task.async;
import fr.altarik.toolbox.asynctasks.AsyncTasks; import fr.altarik.toolbox.task.AltarikRunnable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Date; import java.util.Date;
@ -18,15 +18,18 @@ class AsyncTaskTest {
@Test @Test
void testAsyncOp() throws Exception { void testAsyncOp() throws Exception {
int numberOfTasks = 10000; int numberOfTasks = 10000;
System.out.println("Initializing async tasks worker"); // System.out.println("Initializing async tasks worker");
AsyncTasks worker = AsyncTasks.initialize(1); // only testing on a single worker, otherwise result have a high chance to not be in the order we want AsyncTaskI worker = AsyncTasks.initialize(1); // only testing on a single worker, otherwise result have a high chance to not be in the order we want
Stack<Integer> results = new Stack<>(); Stack<Integer> results = new Stack<>();
for(int i = 0; i < numberOfTasks; i++) { for(int i = 0; i < numberOfTasks; i++) {
System.out.println(log("sending task " + i)); // System.out.println(log("sending task " + i));
AtomicInteger atomicInteger = new AtomicInteger(i); AtomicInteger atomicInteger = new AtomicInteger(i);
worker.addTask(() -> { worker.addTask(new AltarikRunnable() {
System.out.println(log(" task " + atomicInteger.get())); @Override
results.push(atomicInteger.get()); public void run() {
// System.out.println(log(" task " + atomicInteger.get()));
results.push(atomicInteger.get());
}
}); });
} }
worker.close(); // wait until all worker terminated worker.close(); // wait until all worker terminated
@ -35,6 +38,5 @@ class AsyncTaskTest {
expected[i] = i; expected[i] = i;
} }
assertArrayEquals(expected, results.toArray()); assertArrayEquals(expected, results.toArray());
} }
} }

View File

@ -0,0 +1,57 @@
package fr.altarik.toolbox.task.async;
import fr.altarik.toolbox.task.AltarikRunnable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
public class PeriodicAsyncTaskTest {
@Test
void testPeriodicASyncTask() throws Exception {
AsyncPeriodicTasks worker = (AsyncPeriodicTasks) AsyncPeriodicTasks.initialize(1);
Stack<AtomicInteger> results = new Stack<>();
AtomicInteger value1 = new AtomicInteger(1);
AtomicInteger value2 = new AtomicInteger(2);
AltarikRunnable runnable1 = new AltarikRunnable() {
private int i = 0;
@Override
public void run() {
results.add(value1);
i++;
if(i == 2)
cancel();
}
};
AltarikRunnable runnable2 = new AltarikRunnable() {
private int i = 0;
@Override
public void run() {
results.add(value2);
i++;
if(i == 4)
cancel();
}
};
worker.addTask(runnable1, 1, 4);
worker.addTask(runnable2, 0, 2);
for(int i = 0; i < 10; i++) {
worker.scheduler.run();
}
AtomicInteger[] expected = {
value2,
value1,
value2,
value2,
value1,
value2,
value2,
value1
};
worker.close();
Assertions.assertArrayEquals(expected, results.toArray());
}
}

View File

@ -0,0 +1,52 @@
package fr.altarik.toolbox.task.sync;
import fr.altarik.toolbox.task.AltarikRunnable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
class PeriodicSyncTaskTest {
@Test
void testPeriodicSyncTask() {
List<AtomicInteger> results = new ArrayList<>();
PeriodicSyncTask worker = (PeriodicSyncTask) PeriodicSyncTask.initialize();
AtomicInteger value1 = new AtomicInteger(1);
AtomicInteger value2 = new AtomicInteger(2);
worker.addTask(new AltarikRunnable() {
@Override
public void run() {
results.add(value1);
}
}, 1, 3);
worker.addTask(new AltarikRunnable() {
private int i = 0;
@Override
public void run() {
results.add(value2);
if(i++ == 5)
this.cancel();
}
});
for(int i = 0; i < 10; i++) {
worker.scheduler.run();
}
AtomicInteger[] expected = {
value2,
value1,
value2,
value2,
value2,
value1,
value2,
value2,
value1
};
Assertions.assertArrayEquals(expected, results.toArray());
}
}

View File

@ -0,0 +1,49 @@
package fr.altarik.toolbox.task.sync;
import fr.altarik.toolbox.task.AltarikRunnable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
class SyncTaskTest {
@Test
void testOneTimeTask() {
SyncTask worker = (SyncTask) SyncTask.initialize();
List<AtomicInteger> results = new ArrayList<>();
AtomicInteger value1 = new AtomicInteger(1);
AtomicInteger value2 = new AtomicInteger(2);
AltarikRunnable task1 = new AltarikRunnable() {
@Override
public void run() {
results.add(value1);
}
};
AltarikRunnable task2 = new AltarikRunnable() {
@Override
public void run() {
results.add(value2);
}
};
worker.addTask(task1);
worker.scheduler.run();
worker.scheduler.run();
worker.addTask(task2);
worker.addTask(task1);
worker.addTask(task2);
worker.scheduler.run();
worker.addTask(task1);
worker.scheduler.run();
AtomicInteger[] expected = {
value1,
value2,
value1,
value2,
value1
};
Assertions.assertArrayEquals(expected, results.toArray());
}
}

View File

@ -1,31 +1,35 @@
allprojects { import fr.altarik.CreateTag
group = project.maven_group import fr.altarik.ReportDiscord
version = project.maven_version
plugins {
id 'fabric-loom' version '1.6-SNAPSHOT' apply false
} }
subprojects { Properties local = new Properties()
apply plugin: 'java' try {
apply plugin: 'maven-publish' local.load(new FileInputStream(rootProject.file("local.properties")))
sourceCompatibility = JavaVersion.VERSION_17 } catch (IOException ignored) {}
targetCompatibility = JavaVersion.VERSION_17
publishing { group = project.maven_group
publications { version = project.maven_version
mavenJava(MavenPublication) {
from components.java String webhookId = getEnv("DISCORD_PUB_ID", local.getProperty("discord_pub_id"))
} String webhookToken = getEnv("DISCORD_PUB_TOKEN", local.getProperty("discord_pub_token"))
} String repoUrl = "https://repo.altarik.fr/#/" + (project.version.endsWith('SNAPSHOT') ? 'snapshots/' : 'releases/') + project.group.replace(".", "/") + "/" + project.rootProject.name + "/" + project.version
repositories {
maven { var reportConfig = new ReportDiscord.ReportData("https://discord.com/api/", webhookId, webhookToken, project.rootProject.name, "Update " + project.version + " has been published", repoUrl)
name 'altarik'
url 'https://repo.altarik.fr/'.concat(project.version.endsWith('SNAPSHOT') ? 'snapshots/' : 'releases/') String giteaToken = getEnv("GITEA_TOKEN", local.getProperty("gitea_token"))
credentials {
username = project.repo_username var releaseConfig = new CreateTag.CreateReleaseData("https://git.altarik.fr", project.git_owner, project.git_repo, "v" + project.version as String, giteaToken)
password = project.repo_password
}
} allprojects {
} apply plugin: 'maven-publish'
} apply plugin: 'fabric-loom'
group = project.maven_group
version = project.maven_version
repositories { repositories {
maven { maven {
@ -36,7 +40,100 @@ subprojects {
name 'altarik-releases' name 'altarik-releases'
url 'https://repo.altarik.fr/releases/' url 'https://repo.altarik.fr/releases/'
} }
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral() mavenCentral()
} }
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
maven {
name 'altarik'
url 'https://repo.altarik.fr/'.concat(project.version.endsWith('SNAPSHOT') ? 'snapshots/' : 'releases/')
credentials {
username = getEnv("REPO_USERNAME", local.getProperty("repo_username"))
password = getEnv("REPO_PASSWORD", local.getProperty("repo_password"))
}
}
}
}
processResources {
inputs.property "version", project.version
inputs.property "minecraftVersion", project.minecraft_version
inputs.property "loaderVersion", project.loader_version
filesMatching("fabric.mod.json") {
expand inputs.properties
}
}
tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8"
it.options.release = 17
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}"
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
}
java {
withSourcesJar()
withJavadocJar()
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
test {
useJUnitPlatform()
}
} }
dependencies {
implementation project(path: ":Core", configuration: "namedElements")
implementation project(path: ":Database", configuration: "namedElements")
implementation project(path: ":Pagination", configuration: "namedElements")
implementation project(path: ":Tasks", configuration: "namedElements")
include subprojects.collect { project -> project }
}
static def getEnv(String envName, String defaultValue) {
String r = System.getenv(envName)
if(r != null) {
return r
} else {
return defaultValue
}
}
tasks.register("reportToDiscord", ReportDiscord) {
config.set(reportConfig)
}
tasks.register("createTag", CreateTag) {
config.set(releaseConfig)
}
/*jar {
dependsOn subprojects.jar
subprojects.each { project ->
from(project.jar) {
into("META-INF/jars/")
}
}
}*/

11
buildSrc/build.gradle Normal file
View File

@ -0,0 +1,11 @@
plugins {
id 'java'
}
dependencies {
implementation "com.squareup.okhttp3:okhttp:${project.okhttp_version}"
}
repositories {
mavenCentral()
}

View File

@ -0,0 +1 @@
okhttp_version=4.12.0

7
buildSrc/settings.gradle Normal file
View File

@ -0,0 +1,7 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = "buildSrc"

View File

@ -0,0 +1,48 @@
package fr.altarik;
import okhttp3.*;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import java.io.IOException;
public abstract class CreateTag extends DefaultTask {
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
@Input
public abstract Property<CreateReleaseData> getConfig();
@TaskAction
public void create() throws IOException {
CreateReleaseData data = getConfig().get();
String postUrl = data.baseUrl() + "/api/v1/repos/" + data.owner() + "/" + data.repoName() + "/tags";
RequestBody body = RequestBody.create("""
{
"tag_name": \"""" + data.tagName() + "\"" + """
}
""", JSON);
Request request = new Request.Builder()
.url(postUrl)
.post(body)
.header("Authorization", "token " + data.giteaToken())
.build();
try(Response response = client.newCall(request).execute()) {
if(!response.isSuccessful()) {
if(response.code() != 409)
throw new GradleException("Cannot create tag, server answer with code " + response.code() + " and message : " + response.message());
}
}
}
public record CreateReleaseData(String baseUrl, String owner, String repoName, String tagName, String giteaToken) {
}
}

View File

@ -0,0 +1,56 @@
package fr.altarik;
import okhttp3.*;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import java.io.IOException;
public abstract class ReportDiscord extends DefaultTask {
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
@Input
public abstract Property<ReportData> getConfig();
private final OkHttpClient client = new OkHttpClient();
@TaskAction
public void report() throws IOException {
ReportData data = getConfig().get();
String message = data.message();
RequestBody body = RequestBody.create("""
{
"embeds": [
{
"title": "A new update of\s""" + data.projectName() + """
is available",
"description":\s""" + "\"" + message + "\"" + """
,
"url":\s""" + "\"" + data.url() + "\"" + """
}
]
}
""", JSON);
String url = data.baseUrl() + "/webhooks/" + data.webhookId + "/" + data.webhookToken;
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try(Response response = client.newCall(request).execute()) {
getLogger().info("report sent");
if(!response.isSuccessful()) {
throw new GradleException("Discord returned a " + response.code() + " code: " + response.message());
}
}
}
public record ReportData(String baseUrl, String webhookId, String webhookToken, String projectName, String message, String url) {
}
}

View File

@ -1,5 +1,14 @@
org.gradle.jvmargs=-Xmx1G
fabric.loom.multiProjectOptimisation=true
junit_version=5.9.0 junit_version=5.9.0
minecraft_version=1.20.4
yarn_mappings=1.20.4+build.3
loader_version=0.15.6
fabric_version=0.97.1+1.20.4
maven_group=fr.altarik.toolbox maven_group=fr.altarik.toolbox
maven_version=2.0.0-SNAPSHOT maven_version=5.1.0
repo_username=Altarik
repo_password=password git_owner=quentinlegot
git_repo=Toolbox

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

0
gradlew vendored Normal file → Executable file
View File

View File

@ -1,2 +1,16 @@
pluginManagement {
repositories {
mavenCentral()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
rootProject.name = 'Toolbox' rootProject.name = 'Toolbox'
include(':Tasks', ':Database') include(':Tasks')
include(':Database')
include(':Pagination')
include(':Core')

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View File

@ -0,0 +1,35 @@
{
"schemaVersion": 1,
"id": "toolbox",
"version": "${version}",
"name": "Altarik Toolbox",
"description": "Altarik Toolbox, for developers",
"authors": [
"Altarik"
],
"contributors": [
],
"contact": {
"homepage": "https://altarik.fr"
},
"license": "Altarik @ All-Rights-Reserved ",
"icon": "assets/toolbox/icon.png",
"environment": "*",
"depends": {
"fabricloader": "^${loaderVersion}",
"fabric-api": "*",
"minecraft": "${minecraftVersion}",
"java": ">=17",
"toolbox-core": "${version}",
"toolbox-database": "${version}",
"toolbox-pagination": "${version}",
"toolbox-task": "${version}"
},
"custom": {
"modmenu": {
"badges": [ "library" ]
}
}
}