package main import ( "bufio" "encoding/json" "fmt" "io" "log" "net/http" "os" "os/exec" "path/filepath" "time" ) type Message struct { Message string `json:"message"` From string `json:"from"` To string `json:"to"` } // Note: The StreamResponse structure simplified for demonstration. Adjust according to actual requirements. type StreamResponse struct { From string `json:"from"` To string `json:"to"` CreatedAt string `json:"createdAt"` Response string `json:"response"` } const linux = "python3" func createStreamResponse(fromLanguage, toLanguage, message string) string { response := StreamResponse{ From: fromLanguage, To: toLanguage, CreatedAt: time.Now().Format(time.RFC3339), Response: message, } jsonResp, err := json.Marshal(response) if err != nil { return `{"response": "Error in preparing the message."}` } return string(jsonResp) } func streamResponse(w http.ResponseWriter, fromLanguage, toLanguage string, messages <-chan string) { // Set headers for SSE w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) return } for msg := range messages { formattedMessage := createStreamResponse(fromLanguage, toLanguage, msg) fmt.Fprintf(w, "data: %s\n\n", formattedMessage) flusher.Flush() // Ensure client receives the update immediately } } func downloadPackages(w http.ResponseWriter, fromLanguage, toLanguage string) { // Create a channel to send messages from the download process messages := make(chan string) defer close(messages) // Use a goroutine for streaming responses so we can proceed with the download go streamResponse(w, fromLanguage, toLanguage, messages) // Start the python command in the background cmd := exec.Command(linux, "translator/download.py", fromLanguage, toLanguage) output, err := cmd.StdoutPipe() if err != nil { messages <- fmt.Sprintf("Error starting Download: %s", err.Error()) return } err = cmd.Start() if err != nil { messages <- fmt.Sprintf("Error starting Download: %s", err.Error()) return } // Initial message sent to the channel for streaming messages <- "Download started..." // Start a ticker to send "..." every few seconds until we get real output ticker := time.NewTicker(2 * time.Second) go func() { for range ticker.C { messages <- ". . ." } }() // Read the output of the python command and send it to the channel scanner := bufio.NewScanner(output) firstOutputReceived := false for scanner.Scan() { if !firstOutputReceived { // Stop the ticker after receiving the first real output ticker.Stop() firstOutputReceived = true } messages <- scanner.Text() } // Wait for the command to complete err = cmd.Wait() if err != nil { messages <- fmt.Sprintf("Error waiting for Download: %s", err.Error()) return } } func executeTranslator(w http.ResponseWriter, message, fromLanguage, toLanguage string) { // Create a message channel for streaming translation results messages := make(chan string) defer close(messages) // A goroutine will manage sending streamed responses go streamResponse(w, fromLanguage, toLanguage, messages) // Start the python command in the background cmd := exec.Command(linux, "translator/translate.py", message, fromLanguage, toLanguage) output, err := cmd.StdoutPipe() if err != nil { messages <- fmt.Sprintf("Error starting Translation: %s", err.Error()) return } err = cmd.Start() if err != nil { messages <- fmt.Sprintf("Error starting Translation: %s", err.Error()) return } // Read the output of the python command and send it through the channel scanner := bufio.NewScanner(output) for scanner.Scan() { messages <- scanner.Text() // Sends each line of the output to the stream } // Wait for the command to complete err = cmd.Wait() if err != nil { messages <- fmt.Sprintf("Error waiting for Translation: %s", err.Error()) return } } func CheckLanguagesInstalled(fromCode, toCode string) (bool, error) { // Construct the target directory path homeDir, err := os.UserHomeDir() if err != nil { return false, fmt.Errorf("unable to determine the user home directory: %w", err) } targetDir := filepath.Join(homeDir, ".local", "share", "argos-translate", "packages", fromCode+"_"+toCode) // Check if the directory exists _, err = os.Stat(targetDir) if err != nil { if os.IsNotExist(err) { return false, nil // Directory does not exist, indicating the language pair is not installed } return false, fmt.Errorf("error checking for language directory: %w", err) } // The directory exists, indicating the language pair is installed return true, nil } func handleRequest(w http.ResponseWriter, r *http.Request) { // Read the request body if r.Header.Get("Content-Type") != "application/json" { http.Error(w, "Request content type must be application/json", http.StatusBadRequest) return } body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading request body: "+err.Error(), http.StatusInternalServerError) return } defer r.Body.Close() // Unmarshal the JSON data var msg Message err = json.Unmarshal(body, &msg) if err != nil { http.Error(w, "Error unmarshalling JSON: "+err.Error(), http.StatusBadRequest) return } // Check if From and To fields are not longer than 2 letters if len(msg.From) > 2 || len(msg.To) > 2 { http.Error(w, "From and To fields should not be longer than 2 letters.", http.StatusBadRequest) return } else { installed, err := CheckLanguagesInstalled(msg.From, msg.To) if err != nil { if err.Error() == "file not found" { http.Error(w, "file not found "+err.Error(), http.StatusInternalServerError) } else { fmt.Println("An error occurred:", err) } return } if installed { executeTranslator(w, msg.Message, msg.From, msg.To) } else { downloadPackages(w, msg.From, msg.To) } } } func main() { // Define the HTTP handler function http.HandleFunc("", handleRequest) // Get the port number from the environment variable or use a default value port := "53184" if p := os.Getenv("PORT"); p != "" { port = p } // Start the HTTP server fmt.Printf("Listening :%s...\n", port) err := http.ListenAndServe(":"+port, nil) if err != nil { log.Fatalf("Failed to start server: %v", err) } }