mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-09-23 08:20:26 +00:00
185 lines
5.1 KiB
Go
185 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
)
|
|
|
|
const (
|
|
powerReportsDirectoryName = "power_reports"
|
|
powerTimelineFileName = "timeline.jsonl"
|
|
powerEventsFileName = "events.jsonl"
|
|
|
|
powerReportFileContentLimit = 1024 * 1024
|
|
)
|
|
|
|
// File order follows the client convention:
|
|
// sing-box-for-apple Library/Shared/PowerReportManager.swift (availableFiles).
|
|
var powerReportLeadingFileOrder = []string{metadataFileName, configSnapshotFileName, powerTimelineFileName, powerEventsFileName, goLogFileName}
|
|
|
|
func (s *desktopService) ListPowerReports(ctx context.Context, empty *emptypb.Empty) (*OOMReportList, error) {
|
|
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entries, err := os.ReadDir(reportsDirectory)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return &OOMReportList{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
reports := make([]*OOMReportEntry, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
fullPath := filepath.Join(reportsDirectory, entry.Name())
|
|
if !reportOwnedBy(fullPath, userID) {
|
|
continue
|
|
}
|
|
reports = append(reports, &OOMReportEntry{
|
|
Name: entry.Name(),
|
|
RecordedAt: reportTime(fullPath, "startedAt").UnixMilli(),
|
|
IsRead: reportIsRead(fullPath),
|
|
})
|
|
}
|
|
sort.Slice(reports, func(i, j int) bool {
|
|
return reports[i].RecordedAt > reports[j].RecordedAt
|
|
})
|
|
return &OOMReportList{Reports: reports}, nil
|
|
}
|
|
|
|
func (s *desktopService) ReadPowerReport(ctx context.Context, request *OOMReportRequest) (*OOMReportContent, error) {
|
|
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fullPath, err := reportPathForUser(reportsDirectory, request.Name, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
files := make([]*OOMReportFile, 0, len(powerReportLeadingFileOrder))
|
|
for _, fileName := range powerReportLeadingFileOrder {
|
|
content, readError := readFileTail(filepath.Join(fullPath, fileName), powerReportFileContentLimit)
|
|
if readError != nil {
|
|
if os.IsNotExist(readError) {
|
|
continue
|
|
}
|
|
return nil, readError
|
|
}
|
|
files = append(files, &OOMReportFile{
|
|
Name: fileName,
|
|
Content: content,
|
|
})
|
|
}
|
|
entries, err := os.ReadDir(fullPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
profileNames := make([]string, 0, len(entries))
|
|
for _, entry := range entries {
|
|
name := entry.Name()
|
|
if entry.IsDir() || strings.HasPrefix(name, ".") {
|
|
continue
|
|
}
|
|
if slices.Contains(powerReportLeadingFileOrder, name) {
|
|
continue
|
|
}
|
|
profileNames = append(profileNames, name)
|
|
}
|
|
sort.Strings(profileNames)
|
|
for _, name := range profileNames {
|
|
files = append(files, &OOMReportFile{
|
|
Name: name,
|
|
IsProfile: true,
|
|
})
|
|
}
|
|
return &OOMReportContent{Files: files}, nil
|
|
}
|
|
|
|
func readFileTail(path string, limit int64) ([]byte, error) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
info, err := file.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if info.Size() <= limit {
|
|
return os.ReadFile(path)
|
|
}
|
|
_, err = file.Seek(info.Size()-limit, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
content := make([]byte, 0, limit+4)
|
|
content = append(content, "…\n"...)
|
|
buffer := make([]byte, limit)
|
|
n, err := file.Read(buffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(content, buffer[:n]...), nil
|
|
}
|
|
|
|
func (s *desktopService) MarkPowerReportRead(ctx context.Context, request *OOMReportRequest) (*emptypb.Empty, error) {
|
|
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fullPath, err := reportPathForUser(reportsDirectory, request.Name, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = os.WriteFile(filepath.Join(fullPath, readMarkerFileName), nil, 0o600)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (s *desktopService) ExportPowerReport(ctx context.Context, request *OOMReportExportRequest) (*CrashReportArchive, error) {
|
|
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return exportReportArchive(reportsDirectory, request.Name, userID, request.WithConfiguration, request.WithLog, request.Encrypt)
|
|
}
|
|
|
|
func (s *desktopService) DeletePowerReport(ctx context.Context, request *OOMReportRequest) (*emptypb.Empty, error) {
|
|
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fullPath, err := reportPathForUser(reportsDirectory, request.Name, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = os.RemoveAll(fullPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (s *desktopService) DeleteAllPowerReports(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
|
reportsDirectory, userID, err := s.daemon.reportCaller(ctx, powerReportsDirectoryName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = deleteReportsForUser(reportsDirectory, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|