1package main
2
3import (
4	"flag"
5	"fmt"
6	"log"
7	"net"
8	"os"
9	"path/filepath"
10	"reflect"
11	"runtime"
12	"runtime/pprof"
13	"strconv"
14	"strings"
15)
16
17var (
18	envPath  = os.Getenv("PATH")
19	envLevel = os.Getenv("LF_LEVEL")
20)
21
22type arrayFlag []string
23
24var (
25	gSingleMode    bool
26	gClientID      int
27	gHostname      string
28	gLastDirPath   string
29	gSelectionPath string
30	gSocketProt    string
31	gSocketPath    string
32	gLogPath       string
33	gServerLogPath string
34	gSelect        string
35	gConfigPath    string
36	gCommands      arrayFlag
37	gVersion       string
38)
39
40func (a *arrayFlag) Set(v string) error {
41	*a = append(*a, v)
42	return nil
43}
44
45func (a *arrayFlag) String() string {
46	return strings.Join(*a, ", ")
47}
48
49func init() {
50	h, err := os.Hostname()
51	if err != nil {
52		log.Printf("hostname: %s", err)
53	}
54	gHostname = h
55
56	if envLevel == "" {
57		envLevel = "0"
58	}
59}
60
61func exportEnvVars() {
62	os.Setenv("id", strconv.Itoa(gClientID))
63
64	os.Setenv("OPENER", envOpener)
65	os.Setenv("EDITOR", envEditor)
66	os.Setenv("PAGER", envPager)
67	os.Setenv("SHELL", envShell)
68
69	dir, err := os.Getwd()
70	if err != nil {
71		fmt.Fprintf(os.Stderr, "%s\n", err)
72	}
73	os.Setenv("OLDPWD", dir)
74
75	level, err := strconv.Atoi(envLevel)
76	if err != nil {
77		log.Printf("reading lf level: %s", err)
78	}
79
80	level++
81
82	os.Setenv("LF_LEVEL", strconv.Itoa(level))
83}
84
85// used by exportOpts below
86func fieldToString(field reflect.Value) string {
87	kind := field.Kind()
88	var value string
89
90	switch kind {
91	case reflect.Int:
92		value = strconv.Itoa(int(field.Int()))
93	case reflect.Bool:
94		value = strconv.FormatBool(field.Bool())
95	case reflect.Slice:
96		for i := 0; i < field.Len(); i++ {
97			element := field.Index(i)
98
99			if i == 0 {
100				value = fieldToString(element)
101			} else {
102				value += ":" + fieldToString(element)
103			}
104		}
105	default:
106		value = field.String()
107	}
108
109	return value
110}
111
112func exportOpts() {
113	e := reflect.ValueOf(&gOpts).Elem()
114
115	for i := 0; i < e.NumField(); i++ {
116		// Get name and prefix it with lf_
117		name := e.Type().Field(i).Name
118		name = fmt.Sprintf("lf_%s", name)
119
120		// Skip maps
121		if name == "lf_keys" || name == "lf_cmdkeys" || name == "lf_cmds" {
122			continue
123		}
124
125		// Get string representation of the value
126		if name == "lf_sortType" {
127			var sortby string
128
129			switch gOpts.sortType.method {
130			case naturalSort:
131				sortby = "natural"
132			case nameSort:
133				sortby = "name"
134			case sizeSort:
135				sortby = "size"
136			case timeSort:
137				sortby = "time"
138			case ctimeSort:
139				sortby = "ctime"
140			case atimeSort:
141				sortby = "atime"
142			case extSort:
143				sortby = "ext"
144			}
145
146			os.Setenv("lf_sortby", sortby)
147
148			reverse := strconv.FormatBool(gOpts.sortType.option&reverseSort != 0)
149			os.Setenv("lf_reverse", reverse)
150
151			hidden := strconv.FormatBool(gOpts.sortType.option&hiddenSort != 0)
152			os.Setenv("lf_hidden", hidden)
153
154			dirfirst := strconv.FormatBool(gOpts.sortType.option&dirfirstSort != 0)
155			os.Setenv("lf_dirfirst", dirfirst)
156		} else {
157			field := e.Field(i)
158			value := fieldToString(field)
159
160			os.Setenv(name, value)
161		}
162	}
163}
164
165func startServer() {
166	cmd := detachedCommand(os.Args[0], "-server")
167	if err := cmd.Start(); err != nil {
168		log.Printf("starting server: %s", err)
169	}
170}
171
172func checkServer() {
173	if gSocketProt == "unix" {
174		if _, err := os.Stat(gSocketPath); os.IsNotExist(err) {
175			startServer()
176		} else if _, err := net.Dial(gSocketProt, gSocketPath); err != nil {
177			os.Remove(gSocketPath)
178			startServer()
179		}
180	} else {
181		if _, err := net.Dial(gSocketProt, gSocketPath); err != nil {
182			startServer()
183		}
184	}
185}
186
187func main() {
188	showDoc := flag.Bool(
189		"doc",
190		false,
191		"show documentation")
192
193	showVersion := flag.Bool(
194		"version",
195		false,
196		"show version")
197
198	serverMode := flag.Bool(
199		"server",
200		false,
201		"start server (automatic)")
202
203	singleMode := flag.Bool(
204		"single",
205		false,
206		"start a client without server")
207
208	remoteCmd := flag.String(
209		"remote",
210		"",
211		"send remote command to server")
212
213	cpuprofile := flag.String(
214		"cpuprofile",
215		"",
216		"path to the file to write the CPU profile")
217
218	memprofile := flag.String(
219		"memprofile",
220		"",
221		"path to the file to write the memory profile")
222
223	flag.StringVar(&gLastDirPath,
224		"last-dir-path",
225		"",
226		"path to the file to write the last dir on exit (to use for cd)")
227
228	flag.StringVar(&gSelectionPath,
229		"selection-path",
230		"",
231		"path to the file to write selected files on open (to use as open file dialog)")
232
233	flag.StringVar(&gConfigPath,
234		"config",
235		"",
236		"path to the config file (instead of the usual paths)")
237
238	flag.Var(&gCommands,
239		"command",
240		"command to execute on client initialization")
241
242	flag.Parse()
243
244	gSocketProt = gDefaultSocketProt
245	gSocketPath = gDefaultSocketPath
246
247	if *cpuprofile != "" {
248		f, err := os.Create(*cpuprofile)
249		if err != nil {
250			log.Fatalf("could not create CPU profile: %s", err)
251		}
252		if err := pprof.StartCPUProfile(f); err != nil {
253			log.Fatalf("could not start CPU profile: %s", err)
254		}
255		defer pprof.StopCPUProfile()
256	}
257
258	switch {
259	case *showDoc:
260		fmt.Print(genDocString)
261	case *showVersion:
262		fmt.Println(gVersion)
263	case *remoteCmd != "":
264		if err := remote(*remoteCmd); err != nil {
265			log.Fatalf("remote command: %s", err)
266		}
267	case *serverMode:
268		os.Chdir(gUser.HomeDir)
269		gServerLogPath = filepath.Join(os.TempDir(), fmt.Sprintf("lf.%s.server.log", gUser.Username))
270		serve()
271	default:
272		gSingleMode = *singleMode
273
274		if !gSingleMode {
275			checkServer()
276		}
277
278		gClientID = os.Getpid()
279		gLogPath = filepath.Join(os.TempDir(), fmt.Sprintf("lf.%s.%d.log", gUser.Username, gClientID))
280		switch flag.NArg() {
281		case 0:
282			_, err := os.Getwd()
283			if err != nil {
284				fmt.Fprintf(os.Stderr, "%s\n", err)
285				os.Exit(2)
286			}
287		case 1:
288			gSelect = flag.Arg(0)
289		default:
290			fmt.Fprintf(os.Stderr, "only single file or directory is allowed\n")
291			os.Exit(2)
292		}
293
294		exportEnvVars()
295
296		run()
297	}
298
299	if *memprofile != "" {
300		f, err := os.Create(*memprofile)
301		if err != nil {
302			log.Fatal("could not create memory profile: ", err)
303		}
304		runtime.GC()
305		if err := pprof.WriteHeapProfile(f); err != nil {
306			log.Fatal("could not write memory profile: ", err)
307		}
308		f.Close()
309	}
310}
311