// crea un post di risposta al post invocato (con la struttura dati già // inizializzata per bene) func (post *Post) Respond(conv *Conversation, user *User) (*Post, error) { registra := func(post *Post) { err := Data.creaPostSQL(conv, post) if err != nil { logs.Error("Impossibile creare il post sul database\nmotivo:", err.Error()) } err = Data.salvaPostSQL(conv, post.padre) if err != nil { logs.Error("Impossibile aggiornare il post padre sul database\nmotivo:", err.Error()) } } response := conv.NewPost(user, post) if post.rispostaPrincipale == nil { post.rispostaPrincipale = response registra(response) return response, nil } if post.rispostaSecondaria == nil { post.rispostaSecondaria = response registra(response) return response, nil } return nil, NewConversationError("Impossibile attaccare più di due risposte ad un solo post", conv) }
// Inizializzare il server in ascolto per le Sincronizzazione delle conversazioni // // "laddress string" indica su quali indirizza ascoltare e su quale porta func StartServer(laddress string) { ln, err := net.Listen("tcp", laddress) logs.Log("Server in ascolto su: \"", laddress, "\"") if err != nil { logs.Error("Errore nell'aprire la connessione: ", err.Error()) return //TODO handle error } //database.MainConv = database.NewConversation(database.ServerFakeUser) //duplicato! //canale := make(chan byte, 256) codaReadiness := make(chan *Client, 64) codaAccettazioni := make(chan *Client, 64) logs.AggiungiAzioneDiChiusura(func() { close(codaReadiness) close(codaAccettazioni) }) go spedisci(codaAccettazioni, codaReadiness) var spegniti bool = false logs.AggiungiAzioneDiChiusura(func() { spegniti = true ln.Close() }) for !spegniti { conn, err := ln.Accept() if spegniti { return } if err != nil { //TODO fare un pacchetto per la raccolta degli errori logs.Error("Tentativo di connessione non andato a buon fine: ", err.Error()) } go func() { client, gestore := GestisciClient(conn) if client != nil { go gestore(codaReadiness) codaAccettazioni <- client } else { //L'handshaking non è andato a buon fine conn.Close() } }() } }
// Inizializza tutte le operazioni necessarie per sul database func init() { logs.Log("init del database") ServerFakeUser = new(User) ServerFakeUser.ID = 0 ServerFakeUser.Username = "******" serverPassword := sha256.New() serverPassword.Write([]byte("toor")) ServerFakeUser.password = serverPassword.Sum([]byte{}) err := CreateDataBaseSQL() if err != nil { logs.Error(err.Error()) } // MainConv = NewConversation(ServerFakeUser) err = Data.caricaConversazioni() if err != nil { logs.Error("Impossibile caricare le conversazioni vecchie\nmotivo: ", err.Error()) } // err = salvaUtente(ServerFakeUser) // if err != nil { // logs.Error("Impossibile salvare l'utente server nel database\nmotivo: ", err.Error()) // }() Data.CaricaUtentiSQL() if err != nil { logs.Error("Impossibile caricare gli utenti dal database\nmotivo: ", err.Error()) } go func() { var spegniti bool = false logs.AggiungiAzioneDiChiusura(func() { spegniti = true }) tk := time.NewTicker(10 * time.Minute) for !spegniti { <-tk.C err := MainConv.salvaTutteLeConversazioniSQL() if err != nil { logs.Error("Impossibile salvare tutti i post\nmotivo: ", err.Error()) } logs.Log("salvate tutte le conversazioni sul database") //err := MainConv.salvaTuttiIPostSQL(&Data) //if err != nil { // logs.Error("Impossibile salvare tutti i post\nmotivo: ", err.Error()) //} } tk.Stop() }() }
// Questa funzione registra un nuovo utente nel database func (datab *DatabaseRegistration) RegisterNewUser(username, password string) (user *User, success bool) { var present bool success = false if user, present = datab.userNameToId[username]; !present { datab.contatore++ user = new(User) user.ID = datab.contatore user.Username = username hashpassword := sha256.New() hashpassword.Write([]byte(password)) user.password = hashpassword.Sum([]byte{}) err := salvaUtenteSQL(user) if err != nil { logs.Error(err.Error()) return nil, false } datab.userNameToId[username] = user datab.userIDtoUser[user.ID] = user success = true } else { return nil, false } return }
// Fa il flush di tutte le conversazioni sul database // // Ancora finto: salva solamente la conversazione principale func (conv *Conversation) salvaTutteLeConversazioniSQL() error { _, err := updateConvOp.Exec(0) if err != nil { logs.Error("Impossibile salvare la conversazione ", strconv.Itoa(conv.ID), " nel database") return err } return conv.salvaTuttiIPostSQL(&Data) }
// salva un utente nel database func salvaUtenteSQL(user *User) error { _, err := insertUserOp.Exec(user.ID, user.Username, user.password) if err != nil { logs.Error("Impossibile salvare ", user.Username, " nel database") return err } return nil }
// Inizializza la struttura dati del client e crea la funzione per gestire il client // (da partire come goroutine) func GestisciClient(conn net.Conn) (*Client, func(chan *Client)) { fmt.Print("Nuova connessione: ") fmt.Println(conn.RemoteAddr()) client := NewClient(&conn) if client == nil { conn.Close() return nil, nil } return client, func(readiness chan *Client) { var spegniti bool = false logs.AggiungiAzioneDiChiusura(func() { spegniti = true }) for !spegniti { _, size, err := client.stream.ReadRune() if err != nil { logs.Error("connessione interrotta: ", conn.RemoteAddr().String(), "\n\tmotivazione: ", err.Error()) client.gestisciDisconnessione(database.MainConv) return } // for i := 0; i < size; i++ { fmt.Printf("letto carattere di %v byte\n", size) err = client.stream.UnreadRune() if err != nil { logs.Error("impossibile fare UnreadByte: ", conn.RemoteAddr().String(), "\n\tmotivazione: ", err.Error()) client.gestisciDisconnessione(database.MainConv) return } // } readiness <- client <-client.blocco } (*client.conn).Close() } }
// Fa il flush sul database di tutti i post relativi alla conversazione func (conv *Conversation) salvaTuttiIPostSQL(data *DatabaseRegistration) error { messaggio := "salvataggio di tutti i post sul database" logs.Log(messaggio) err := conv.testaPost.salvaPostRicSQL(conv, data) if err != nil { logs.Error(messaggio + " fallito") } else { logs.Log(messaggio + " riuscito") } return err }
// Svolge l'handshake con il client. Ritorna true se l'handshake ha avuto successo func (client *Client) handshake() bool { keepAlive, err := client.leggiEseguiComando() if err != nil { logs.Error(err.Error()) return false } if !keepAlive { return false } //REGISTRAZIONE DELL'UTENTE ALLA CONVERSAZIONE //TODO scegliere la conversazione a cui connettersi -.- err2 := database.MainConv.NewUserConnection(client.user) if err2 != nil { //CONTROLLO SE L'UTENTE È GIÀ CONNESSO logs.Error("impossibile connettere: ", err2.Error()) return false } client.stream.WriteString(strconv.Itoa(client.user.ID)) client.stream.WriteRune('\\') //spedisci lo stato attuale della conversazione client.stream.WriteString("\\U0\\") client.stream.WriteString(database.MainConv.GetComplete(false)) //Invia le posizioni di tutte quante le persone connesse client.stream.WriteString(database.MainConv.GetAllPositionString()) //invia l'utente attivo client.stream.WriteString("\\U" + strconv.Itoa(database.MainConv.UtenteAttivo) + "\\") client.stream.Flush() return true }
// Fa il flush del post nel database func (datab *DatabaseRegistration) salvaPostSQL(conv *Conversation, post *Post) error { //logs.Log("salvo post ", strconv.Itoa(post.idPost)) var idPadre, idRPri, idRSec int = -1, -1, -1 if post.padre != nil { idPadre = post.padre.idPost } if post.rispostaPrincipale != nil { idRPri = post.rispostaPrincipale.idPost } if post.rispostaSecondaria != nil { idRSec = post.rispostaSecondaria.idPost } //campi: id padre first_response second_response text _, err := updatePostOp.Exec(post.idPost, idPadre, idRPri, idRSec, post.testo.GetComplete(false)) if err != nil { logs.Error("Impossibile salvare il post ", strconv.Itoa(post.idPost), " nel database") return err } return nil }
// crea il post dentro il database. Ogni post deve essere creato prima di // potere essere salvato. func (datab *DatabaseRegistration) creaPostSQL(conv *Conversation, post *Post) error { logs.Log("creo post ", strconv.Itoa(post.idPost)) var idPadre, idRPri, idRSec int = -1, -1, -1 if post.padre != nil { idPadre = post.padre.idPost } if post.rispostaPrincipale != nil { idRPri = post.rispostaPrincipale.idPost } if post.rispostaSecondaria != nil { idRSec = post.rispostaSecondaria.idPost } _, err := insertPostOp.Exec(post.idPost, conv.ID, post.testo.GetComplete(false), idPadre, idRPri, idRSec) if err != nil { logs.Error("Impossibile creare il post ", strconv.Itoa(post.idPost), " nel database") return err } return nil }
// Elimina dalla Superstringa un tot caratteri nella posizione data. // Elimina a partire dalla posizione data in poi func (lista *SuperString) DelElem(pos int, howmany int) { // fmt.Println("Cercando di eliminare: pos=", pos, " howmany=", howmany) if pos < 0 { logs.Error("che cazzo stai cercando di eliminare???") return } //roRebuildTotal = true posAttuale := lista.testa for pos > posAttuale.size { pos -= posAttuale.size posAttuale = posAttuale.succ if posAttuale == nil { logs.Error("Cercando di eliminare fuori dalla stringa, l'eliminazione verrà ignorata") return } } //elimina prima stringa quanti_eliminare := howmany if pos+howmany <= posAttuale.size { //elimina solo posizione attuale // fmt.Println("Elimina solo posizione attuale") //DEBUG //vecchio := posAttuale.elemento //posAttuale.elemento = strings.Join([]string{vecchio[0:pos], vecchio[pos+howmany : posAttuale.size]}, "") //remove dalla stringa corrente if (posAttuale.size - quanti_eliminare) == 0 { lista.delSingleElem(posAttuale) return } posAttuale.elemento = removeFromRunes(posAttuale.elemento, posAttuale.size, pos, howmany) posAttuale.size -= quanti_eliminare return } quanti_eliminare = posAttuale.size - pos posAttuale.elemento = removeFromRunes(posAttuale.elemento, posAttuale.size, pos, quanti_eliminare) posAttuale.size -= quanti_eliminare if posAttuale.size == 0 { tmp := posAttuale.succ lista.delSingleElem(posAttuale) posAttuale = tmp if posAttuale == nil { posAttuale = lista.testa } } else { posAttuale = posAttuale.succ } //elimina le stringhe di mezzo quanti_eliminare = howmany - quanti_eliminare if posAttuale == nil { logs.Error("ma che cazzo succede? posAttuale == nil?") return } for posAttuale.succ != nil && quanti_eliminare >= posAttuale.size { // fmt.Println("Elimino stringa di mezzo") //DEBUG quanti_eliminare -= posAttuale.size tmp := posAttuale.succ lista.delSingleElem(posAttuale) posAttuale = tmp } //elimina ultima stringa if posAttuale.size == quanti_eliminare { lista.delSingleElem(posAttuale) } else { // fmt.Println("Elimino da ultima stringa") //DEBUG posAttuale.elemento = removeFromRunes(posAttuale.elemento, posAttuale.size, 0, quanti_eliminare) posAttuale.size -= quanti_eliminare } }
// Questa funzione entra in un ciclo e non vi esce finché il canale di input non // viene chiuso. Quello che fa è un parsing dei caratteri dal canale di input // controllando le azioni da svolgere e riversare (intelligentemente) sul canale // di output quello che deve essere spedito ai vari client. // // This function should run as goroutine func gestisciTestoConversazione(input chan rune, output chan string) { activeUser := database.Data.GetUserByID(0) var errore error var versoClient []rune // cursor := 0 for c := range input { // c := <-input // fmt.Println("input",string(c)) switch c { case '\\': //caso di carattere di controllo cc := <-input // fmt.Println("input",string(cc)) switch cc { case 'K': parent, cu := mangiaIntero(input) if cu != '\\' { errore = TranslationError{"ERRORE lettura stream: carattere di controllo mangiato != '\\'"} break } var postRID int postRID, errore = database.MainConv.Respond(parent, activeUser) versoClient = []rune{'\\', 'K'} versoClient = append(versoClient, ([]rune(strconv.Itoa(parent)))...) versoClient = append(versoClient, '\\') versoClient = append(versoClient, ([]rune(strconv.Itoa(postRID)))...) versoClient = append(versoClient, '\\') case 'P': postCursor, cu := mangiaIntero(input) if cu != '\\' { errore = TranslationError{"ERRORE lettura stream: carattere di controllo mangiato != '\\'"} break } errore = database.MainConv.ChangePost(activeUser, postCursor) versoClient = []rune{'\\', cc} versoClient = append(versoClient, ([]rune(strconv.Itoa(postCursor)))...) versoClient = append(versoClient, '\\') case 'C': cursor, cu := mangiaIntero(input) if cu != '\\' { errore = TranslationError{"ERRORE lettura stream: carattere di controllo mangiato != '\\'"} break } errore = database.MainConv.ChangePos(activeUser, cursor) versoClient = []rune{'\\', cc} versoClient = append(versoClient, ([]rune(strconv.Itoa(cursor)))...) versoClient = append(versoClient, '\\') case 'D': howmany, cu := mangiaIntero(input) if cu != '\\' { errore = TranslationError{"ERRORE lettura stream: carattere di controllo mangiato != '\\'"} break } versoClient = []rune{'\\', cc} versoClient = append(versoClient, ([]rune(strconv.Itoa(howmany)))...) versoClient = append(versoClient, '\\') //cursor -= howmany //database.MainConv.TestaPost.Text(activeUser).DelElem(cursor, howmany) errore = database.MainConv.DelElem(activeUser, howmany) case 'U': newUserID, cu := mangiaIntero(input) if cu != '\\' { errore = TranslationError{"ERRORE lettura stream: carattere di controllo mangiato != '\\'"} break } activeUser = database.Data.GetUserByID(newUserID) versoClient = []rune{'\\', cc} versoClient = append(versoClient, ([]rune(strconv.Itoa(newUserID)))...) versoClient = append(versoClient, '\\') case '\\': //database.MainConv.TestaPost.Text(activeUser).InsSingleElem('\\', cursor) errore = database.MainConv.InsElem(activeUser, []rune{'\\'}) versoClient = []rune{'\\'} //cursor++ default: fmt.Println("ERRORE azione", cc, "non disponibile") } if errore != nil { logs.Error("errore nel lavorare sulla superstringa\n\tultimo comando: ", string(cc), "\n\tmotivo: ", errore.Error()) errore = nil } else { // for i := range versoClient { // output <- versoClient[i] // } output <- string(versoClient) } default: //database.MainConv.TestaPost.Text(activeUser).InsSingleElem(c, cursor) errore = database.MainConv.InsElem(activeUser, []rune{c}) output <- string(c) //cursor++ //TODO anche qui si può pensare ad un flasher a tempo per minimizzare il lavoro su superstring } if errore != nil { logs.Error("errore nel lavorare sulla superstringa\n\t ultimo carattere: ", string(c), "\n\tmotivo: ", errore.Error()) errore = nil } // fmt.Println("---", database.MainConv.TestaPost.Text(activeUser).GetComplete(true), "---") //DEBUG fmt.Println(database.MainConv.GetComplete(true)) //DEBUG } }
// Funzione che innesta tutta la procedura per il parsing di quello ricevuto // dai client. //TODO migliora la documentazione func flasher(codaCiclica *list.List, readiness chan *Client) { tempoDaAspettare := 30 * time.Millisecond var lastActiveUser int = -1 input := make(chan rune, 256) output := make(chan string, 256) logs.AggiungiAzioneDiChiusura(func() { close(input) close(output) }) go gestisciTestoConversazione(input, output) // semplice funzione che rispedisce tutto quello che passa // dal canale di output sui socket delle connessioni go func() { for buffer := range output { for e := codaCiclica.Front(); e != nil; e = e.Next() { client := e.Value.(*Client) client.stream.WriteString(buffer) client.stream.Flush() } } }() var spegniti bool = false logs.AggiungiAzioneDiChiusura(func() { spegniti = true }) for !spegniti { start := time.Now() quanti := len(readiness) for i := 0; i < quanti; i++ { clientAttivo := <-readiness var chiSonoString string if lastActiveUser != clientAttivo.user.ID { chiSonoString = strings.Join([]string{"\\U", strconv.Itoa(clientAttivo.user.ID), "\\"}, "") lastActiveUser = clientAttivo.user.ID database.MainConv.UtenteAttivo = lastActiveUser } else { chiSonoString = "" } chiSonoSSize := len(chiSonoString) //leggi cosa spedire var daLeggere int = clientAttivo.stream.Reader.Buffered() buffer := make([]rune, chiSonoSSize, daLeggere+chiSonoSSize) var err error var letto rune var size, i, j int for i, j = chiSonoSSize, 0; i < chiSonoSSize+daLeggere && j < daLeggere; i++ { letto, size, err = clientAttivo.stream.ReadRune() buffer = append(buffer, letto) j += size //fmt.Printf("#####size:_%v_ carattere:_%v_%v_\n",strconv.Itoa(size),string(buffer[i]),buffer[i]) if err != nil { //TODO gestisci errore logs.Error("Errore nel leggere dalla rete") clientAttivo.blocco <- 1 //TODO dovresti chiudere il canale e tutto quanto clientAttivo.gestisciDisconnessione(database.MainConv) break } } clientAttivo.blocco <- 0 //prepara il buffer da spedire for i := 0; i < chiSonoSSize; i++ { buffer[i] = []rune(chiSonoString)[i] } // buffer = buffer[:i] //spedisci for i := 0; i < len(buffer); i++ { input <- buffer[i] } } duration := time.Since(start) if duration <= tempoDaAspettare { time.Sleep(tempoDaAspettare - duration) } } }
// Funzione che inizializza tutte le cose necessarie per collegare il // database SQL, e lo collega func CreateDataBaseSQL() error { var err error db, err = sql.Open("postgres", "dbname=thinkzoneDB user=thinkzone") if err != nil { logs.Error("Impossibile aprire il database") return err } /* for i := range createDbSqlScript { _, err = db.Exec(createDbSqlScript[i]) if err != nil { if "result error: ERROR: relation \"t_user\" already exists\n" != err.Error() { logs.Error("Impossibile creare le tabelle del database\nmotivo: _", err.Error(), "_") return err } break } } */ insertUserOp, err = db.Prepare(insertUser) if err != nil { // logs.Error("Impossibile salvare gli utenti nel database\nmotivo: ", err.Error()) return err } insertPostOp, err = db.Prepare(insertPost) if err != nil { // logs.Error("Impossibile creare i post nel database\nmotivo: ", err.Error()) return err } updatePostOp, err = db.Prepare(updatePost) if err != nil { // logs.Error("Impossibile salvare i post nel database\nmotivo: ", err.Error()) return err } insertConvOp, err = db.Prepare(insertConv) if err != nil { // logs.Error("Impossibile salvare le conversazioni nel database\nmotivo: ", err.Error()) return err } updateConvOp, err = db.Prepare(updateConv) if err != nil { return err } logs.AggiungiAzioneDiChiusura(func() { if insertUserOp != nil { insertUserOp.Close() } if insertConvOp != nil { err := MainConv.salvaTutteLeConversazioniSQL() if err != nil { logs.Error("Impossibile salvare tutti i post\nmotivo: ", err.Error()) } insertConvOp.Close() } if insertPostOp != nil { insertPostOp.Close() } if updatePostOp != nil { updatePostOp.Close() } if updateConvOp != nil { updateConvOp.Close() } return }) return nil }