blob: 5641c8edfb7f4fd256bd893a495fde9db1660821 [file] [log] [blame]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package localstate provides utilities for accessing the browser's Local
// State file.
package localstate
import (
"encoding/json"
"io/ioutil"
"os"
"strings"
"chromiumos/tast/errors"
"chromiumos/tast/local/chrome/browser"
)
const (
localStatePathAsh = "/home/chronos/Local State"
localStatePathLacros = "/home/chronos/user/lacros/Local State"
)
// Unmarshal performs json.Unmarshal on the contents of the browser's Local
// State file.
func Unmarshal(bt browser.Type, out interface{}) error {
var localStatePath = localStatePathAsh
if bt == browser.TypeLacros {
localStatePath = localStatePathLacros
}
b, err := ioutil.ReadFile(localStatePath)
if err != nil {
return errors.Wrap(err, "failed to read Local State file")
}
if err := json.Unmarshal(b, out); err != nil {
return errors.Wrap(err, "failed to unmarshal Local State")
}
return nil
}
// UnmarshalPref returns the unmarshaled value of a preference from the
// browser's Local State file. The preference name is a string such as
// "foo.bar.baz".
func UnmarshalPref(bt browser.Type, pref string) (interface{}, error) {
path := strings.Split(pref, ".")
var localState interface{}
if err := Unmarshal(bt, &localState); err != nil {
return nil, errors.Wrap(err, "failed to retrieve Local State contents")
}
for i, key := range path {
unexpectedValueError := func() error {
errPref := strings.Join(path[:i], ".")
return errors.Errorf("unexpected value in Local State at %s: %v", errPref, localState)
}
dict, ok := localState.(map[string]interface{})
if !ok {
return nil, unexpectedValueError()
}
value, ok := dict[key]
if !ok {
return nil, unexpectedValueError()
}
localState = value
}
return localState, nil
}
// Marshal will update Local State with localState.
// localState includes pref names and pref values that will be written to local state.
// pref name with dot format "foo.bar.baz" is not allowed in localState interface.
// localState could include nested map type {"foo":{"bar":{"baz": 1234}}}.
func Marshal(bt browser.Type, localState interface{}) error {
path := localStatePathAsh
if bt == browser.TypeLacros {
path = localStatePathLacros
}
s, err := json.Marshal(localState)
if err != nil {
return errors.Wrap(err, "failed to marshal Local State")
}
if err := ioutil.WriteFile(path, s, 0644); err != nil {
return errors.Wrap(err, "failed to write Local State")
}
return nil
}
// MarshalPref will update the pref with val in local state.
// pref name could have format "foo.bar.baz". Each component separated by '.' will
// be used as key of the dict. pref must not be an empty string.
func MarshalPref(bt browser.Type, pref string, val interface{}) error {
if pref == "" {
return errors.New("perf name cannot be empty, use Marshal if you're overwriting the entire file content")
}
var localStatePath = localStatePathAsh
if bt == browser.TypeLacros {
localStatePath = localStatePathLacros
}
var localState interface{}
if _, err := os.Stat(localStatePath); err == nil {
if err := Unmarshal(bt, &localState); err != nil {
return errors.Wrap(err, "failed to retrieve Local State contents")
}
} else if errors.Is(err, os.ErrNotExist) {
// If local state does not exist, create it. Initialize localState by an empty
// map
localState = make(map[string]interface{})
} else {
return errors.New("failed to retrieve Local State contents")
}
dict, ok := localState.(map[string]interface{})
if !ok {
return errors.New("localState has an unexpected value type")
}
keys := strings.Split(pref, ".")
for _, key := range keys[:len(keys)-1] {
e, ok := dict[key]
if !ok {
e = make(map[string]interface{})
dict[key] = e
}
dict, ok = e.(map[string]interface{})
if !ok {
return errors.New("failed to read Local State content")
}
}
dict[keys[len(keys)-1]] = val
if err := Marshal(bt, localState); err != nil {
return errors.Wrap(err, "failed to wirte Local State contents")
}
return nil
}