blob: 1516670709833acd32ccb1da2fd4afabd90f38ed [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import * as process from 'process';
// Functions to read/write metric properties (e.g. UUID, opt-in/out status), which are stored in
// ~/.fuchsia/metrics/<property-name>
/**
* Get the property with the given name.
* @param name the name of the property
* @returns the property value without possible leading or trailing newlines, or undefined if the
* property does not exist
*/
export async function get(name: string): Promise<string | undefined> {
try {
return (await fs.readFile(getMetricPropertyPath(name))).toString().trim();
} catch {
return undefined;
}
}
/**
* Similar to get(), but returns a boolean.
* @param name the name of the property
* @returns true if and only if get() would return "1"
*/
export async function getBoolean(name: string): Promise<boolean | undefined> {
const content = await get(name);
if (content === undefined) {
return undefined;
}
return content === '1';
}
/**
* Set the property with the given name to the given value.
* @param name the name of the property
* @param value the value to set for the property
*/
export async function set(name: string, value: string): Promise<void> {
try {
await fs.mkdir(getMetricDirectory(), { recursive: true, mode: 0o700 });
await fs.writeFile(getMetricPropertyPath(name), value + '\n', { mode: 0o600 });
} catch (err) {
if (err instanceof Error) {
console.log(`Warning: unable to set analytics property ${name}`);
}
}
}
/**
* Similar to set but for boolean value
* @param name the name of the property
* @param value the boolean value to set for the property
*/
export async function setBoolean(name: string, value: boolean): Promise<void> {
await set(name, value ? '1' : '0');
}
/**
* Delete the property with the given name.
* @param name the name of the property
*/
export async function deleteProperty(name: string): Promise<void> {
try {
await fs.rm(getMetricPropertyPath(name), { force: true });
} catch (err) {
if (err instanceof Error) {
console.log(`Warning: unable to delete analytics property ${name}`);
}
}
}
/**
* Check the existence of the property with the given name.
* @param name the name of the property
* @returns true if the property exists
*/
export async function exists(name: string): Promise<boolean> {
try {
await fs.access(getMetricPropertyPath(name));
return true;
} catch {
return false;
}
}
/**
* See below:
* @returns the base directory to put 'Fuchsia/metrics' folder
*/
function getMetricBaseDirectory(): string {
if (os.platform() === 'darwin') {
return path.join(os.homedir(), 'Library', 'Application Support');
}
if (process.env.XDG_DATA_HOME !== undefined) {
return process.env.XDG_DATA_HOME;
}
return path.join(os.homedir(), '.local', 'share');
}
/**
* See below:
* @returns the directory to store persistent analytics status
*/
export function getMetricDirectory(): string {
return path.join(getMetricBaseDirectory(), 'Fuchsia', 'metrics');
}
/**
* See below:
* @returns old directory to store persistent analytics status
*/
function getOldMetricDirectory(): string {
return path.join(os.homedir(), '.fuchsia', 'metrics');
}
/**
* Get the file path for storing a specific metric property.
* @param name the name of the property
* @returns the path of the storage file
*/
function getMetricPropertyPath(name: string): string {
return path.join(getMetricDirectory(), name);
}
/**
* Migrate persistence storage location from $HOME to $XDG_DATA_HOME
*/
export async function migrateMetricDirectory(): Promise<void> {
const metricDirectory = getMetricDirectory();
try {
await fs.access(metricDirectory);
// no need to migrate as the new folder already exists
return;
} catch {
// continue to the next try
}
const oldMetricDirectory = getOldMetricDirectory();
try {
await fs.access(oldMetricDirectory);
} catch {
// no need to migrate as the old folder does not exist
return;
}
try {
await fs.mkdir(path.dirname(getMetricDirectory()), { recursive: true, mode: 0o700 });
await fs.rename(oldMetricDirectory, metricDirectory);
// TODO(fxb/113028): remove symlinking and related tests when ready
await fs.symlink(metricDirectory, oldMetricDirectory, 'dir');
} catch {
// We have tried our best and have to give up the migration. However, normal usage of
// analytics would not be affected. The only consequence is that migrated and non-migrated
// tools might have inconsistent analytics status.
}
}