Skip to content

Commit

Permalink
Introducing JFrog CLI AI commands assistant (#2703)
Browse files Browse the repository at this point in the history
  • Loading branch information
sverdlov93 authored Sep 24, 2024
1 parent 360095a commit 2c5f949
Show file tree
Hide file tree
Showing 15 changed files with 284 additions and 50 deletions.
2 changes: 1 addition & 1 deletion build/npm/v2-jf/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/npm/v2-jf/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jfrog-cli-v2-jf",
"version": "2.68.0",
"version": "2.69.0",
"description": "🐸 Command-line interface for JFrog Artifactory, Xray, Distribution, Pipelines and Mission Control 🐸",
"homepage": "https://github.com/jfrog/jfrog-cli",
"preferGlobal": true,
Expand Down
2 changes: 1 addition & 1 deletion build/npm/v2/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/npm/v2/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jfrog-cli-v2",
"version": "2.68.0",
"version": "2.69.0",
"description": "🐸 Command-line interface for JFrog Artifactory, Xray, Distribution, Pipelines and Mission Control 🐸",
"homepage": "https://github.com/jfrog/jfrog-cli",
"preferGlobal": true,
Expand Down
2 changes: 1 addition & 1 deletion docs/general/ai/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package ai
var Usage = []string{"how"}

func GetDescription() string {
return "This AI-based interface converts your natural language inputs into fully functional JFrog CLI commands. This is an interactive command that accepts no arguments."
return "An AI-powered interface that converts natural language inputs into AI-generated JFrog CLI commands."
}
36 changes: 32 additions & 4 deletions general/ai/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import (
type ApiCommand string

const (
cliAiAppApiUrl = "https://cli-ai-app-stg.jfrog.info/api/"
cliAiAppApiUrl = "https://cli-ai-app.jfrog.io/api/"
askRateLimitHeader = "X-JFrog-CLI-AI"
// The latest version of the terms and conditions for using the AI interface. (https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli/cli-ai/terms)
aiTermsRevision = 1
)

type ApiType string
Expand All @@ -41,9 +43,16 @@ func HowCmd(c *cli.Context) error {
if c.NArg() > 0 {
return cliutils.WrongNumberOfArgumentsHandler(c)
}
log.Output(coreutils.PrintLink("This AI-based interface converts your natural language inputs into fully functional JFrog CLI commands.\n" +
log.Output(coreutils.PrintLink("This AI-powered interface converts natural language inputs into AI-generated JFrog CLI commands.\n" +
"For more information about this interface, see https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli/cli-ai\n" +
"NOTE: This is an experimental version and it supports mostly Artifactory and Xray commands.\n"))

// Ask the user to agree to the terms and conditions. If the user does not agree, the command will not proceed.
// Ask this only once per JFrog CLI installation, unless the terms are updated.
if agreed, err := handleAiTermsAgreement(); err != nil || !agreed {
return err
}

for {
var question string
scanner := bufio.NewScanner(os.Stdin)
Expand Down Expand Up @@ -147,12 +156,12 @@ func sendRestAPI(apiType ApiType, content interface{}) (response string, err err
if err = errorutils.CheckResponseStatus(resp, http.StatusOK); err != nil {
switch resp.StatusCode {
case http.StatusInternalServerError:
err = errorutils.CheckErrorf("CLI-AI model endpoint is not available. Please try again later.")
err = errorutils.CheckErrorf("JFrog CLI-AI model endpoint is not available. Please try again later.")
case http.StatusNotAcceptable:
err = errorutils.CheckErrorf("The system is currently handling multiple requests from other users\n" +
"Please try submitting your question again in a few minutes. Thank you for your patience!")
default:
err = errorutils.CheckErrorf("CLI-AI server is not available. Please check your network or try again later. Note that the this command is supported while inside JFrog's internal network only.\n" + err.Error())
err = errorutils.CheckErrorf("JFrog CLI-AI server is not available. Please check your network or try again later:\n" + err.Error())
}
return
}
Expand All @@ -176,3 +185,22 @@ func sendRestAPI(apiType ApiType, content interface{}) (response string, err err
response = strings.TrimSpace(string(body))
return
}

func handleAiTermsAgreement() (bool, error) {
latestTermsVer, err := cliutils.GetLatestAiTermsRevision()
if err != nil {
return false, err
}
if latestTermsVer == nil || *latestTermsVer < aiTermsRevision {
if !coreutils.AskYesNo("By using this interface, you agree to the terms of JFrog's AI Addendum on behalf of your organization as an active JFrog customer.\n"+
"Review these terms at "+coreutils.PrintLink("https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli/cli-ai/terms")+
"\nDo you agree?", false) {
return false, nil
}
if err = cliutils.SetLatestAiTermsRevision(aiTermsRevision); err != nil {
return false, err
}
log.Output()
}
return true, nil
}
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ replace (
require (
github.com/agnivade/levenshtein v1.1.1
github.com/buger/jsonparser v1.1.1
github.com/docker/docker v27.2.1+incompatible
github.com/docker/docker v27.3.1+incompatible
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/jfrog/archiver/v3 v3.6.1
github.com/jfrog/build-info-go v1.10.0
Expand Down Expand Up @@ -148,11 +148,11 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/sdk v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.opentelemetry.io/otel v1.30.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect
go.opentelemetry.io/otel/metric v1.30.0 // indirect
go.opentelemetry.io/otel/sdk v1.30.0 // indirect
go.opentelemetry.io/otel/trace v1.30.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/mod v0.21.0 // indirect
Expand Down
40 changes: 20 additions & 20 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -707,8 +707,8 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI=
github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
Expand Down Expand Up @@ -1195,18 +1195,18 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8=
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
Expand Down Expand Up @@ -1836,15 +1836,15 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo=
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
Expand Down Expand Up @@ -1886,8 +1886,8 @@ google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
Expand Down
2 changes: 0 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,10 @@ func getCommands() ([]cli.Command, error) {
Action: login.LoginCmd,
},
{
Hidden: true,
Name: "how",
Usage: aiDocs.GetDescription(),
HelpName: corecommon.CreateUsage("how", aiDocs.GetDescription(), aiDocs.Usage),
BashComplete: corecommon.CreateBashCompletionFunc(),
Category: otherCategory,
Action: ai.HowCmd,
},
{
Expand Down
1 change: 1 addition & 0 deletions npm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func testNpm(t *testing.T, isLegacy bool) {
assert.NoError(t, err)
return
}
log.Info("npm version:", npmVersion.GetVersion())
isNpm7 := isNpm7(npmVersion)

// Temporarily change the cache folder to a temporary folder - to make sure the cache is clean and dependencies will be downloaded from Artifactory
Expand Down
2 changes: 1 addition & 1 deletion utils/cliutils/cli_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "time"

const (
// General CLI constants
CliVersion = "2.68.0"
CliVersion = "2.69.0"
ClientAgent = "jfrog-cli-go"

// CLI base commands constants:
Expand Down
136 changes: 136 additions & 0 deletions utils/cliutils/persistence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package cliutils

import (
"encoding/json"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
"os"
"path/filepath"
gosync "sync"
)

const persistenceFileName = "persistence.json"

// PersistenceFile holds various indicators that need to be persisted between CLI runs
type PersistenceFile struct {
LatestCliVersionCheckTime *int64 `json:"latestCliVersionCheckTime,omitempty"`
LatestAiTermsRevision *int `json:"latestAiTermsRevision,omitempty"`
}

var (
persistenceFilePath string
fileLock gosync.Mutex
)

// getPersistenceFilePath ensures that the persistence file path is initialized
func getPersistenceFilePath() error {
if persistenceFilePath == "" {
homeDir, err := coreutils.GetJfrogHomeDir()
if err != nil {
return errorutils.CheckErrorf("failed to get JFrog home directory: " + err.Error())
}
persistenceFilePath = filepath.Join(homeDir, persistenceFileName)
}
return nil
}

// setCliLatestVersionCheckTime updates the latest version check time in the persistence file
func setCliLatestVersionCheckTime(timestamp int64) error {
fileLock.Lock()
defer fileLock.Unlock()

info, err := getPersistenceInfo()
if err != nil {
return err
}

info.LatestCliVersionCheckTime = &timestamp
return setPersistenceInfo(info)
}

// getLatestCliVersionCheckTime retrieves the latest version check time from the persistence file
func getLatestCliVersionCheckTime() (*int64, error) {
fileLock.Lock()
defer fileLock.Unlock()

info, err := getPersistenceInfo()
if err != nil {
return nil, err
}

return info.LatestCliVersionCheckTime, nil
}

// SetLatestAiTermsRevision updates the AI terms version in the persistence file
func SetLatestAiTermsRevision(version int) error {
fileLock.Lock()
defer fileLock.Unlock()

info, err := getPersistenceInfo()
if err != nil {
return err
}

info.LatestAiTermsRevision = &version
return setPersistenceInfo(info)
}

// GetLatestAiTermsRevision retrieves the AI terms version from the persistence file
func GetLatestAiTermsRevision() (*int, error) {
fileLock.Lock()
defer fileLock.Unlock()

info, err := getPersistenceInfo()
if err != nil {
return nil, err
}

return info.LatestAiTermsRevision, nil
}

// getPersistenceInfo reads the persistence file, creates it if it doesn't exist, and returns the persisted info
func getPersistenceInfo() (*PersistenceFile, error) {
if err := getPersistenceFilePath(); err != nil {
return nil, err
}
if exists, err := fileutils.IsFileExists(persistenceFilePath, false); err != nil || !exists {
if err != nil {
return nil, err
}
// Create an empty persistence file if it doesn't exist
pFile := &PersistenceFile{}
if err = setPersistenceInfo(pFile); err != nil {
return nil, errorutils.CheckErrorf("failed while attempting to initialize persistence file: " + err.Error())
}
return pFile, nil
}

data, err := os.ReadFile(persistenceFilePath)
if err != nil {
return nil, errorutils.CheckErrorf("failed while attempting to read persistence file: " + err.Error())
}

var info PersistenceFile
if err = json.Unmarshal(data, &info); err != nil {
return nil, errorutils.CheckErrorf("failed while attempting to parse persistence file: " + err.Error())
}

return &info, nil
}

// setPersistenceInfo writes the given info to the persistence file
func setPersistenceInfo(info *PersistenceFile) error {
if err := getPersistenceFilePath(); err != nil {
return err
}
data, err := json.MarshalIndent(info, "", " ")
if err != nil {
return errorutils.CheckErrorf("failed while attempting to create persistence file: " + err.Error())
}

if err = os.WriteFile(persistenceFilePath, data, 0644); err != nil {
return errorutils.CheckErrorf("failed while attempting to write persistence file: " + err.Error())
}
return nil
}
Loading

0 comments on commit 2c5f949

Please sign in to comment.