/// <summary> /// La funzione deve gestire la creazione di un nuovo file. /// </summary> public override bool Handle() { if(this.Stopped) return false; if (String.IsNullOrEmpty(base.FullPath)){ throw new Exception("Path e nome del file non validi."); } using(TcpClient connection = new TcpClient()){ connection.Connect(UserCredentials.Instance.Host, UserCredentials.Instance.Port); using (StreamReader sr = new StreamReader(connection.GetStream())) using (StreamWriter sw = new StreamWriter(connection.GetStream())) { //sr.BaseStream.ReadTimeout = 10000; //sw.BaseStream.WriteTimeout = 10000; /************************************ Inizio della funzione ***************************************/ string username = UserCredentials.Instance.Username; string accesstoken = UserCredentials.Instance.AccessToken; string allPath = this.FullPath; string subcmd = ProtocolStrings.SUBCMD_CREATE_MODIFY; long client_vfsversion = SyncToken.Instance.VFSVersion; try { string syncroPath = UserUtils.GetRootPath(); if (!allPath.StartsWith(syncroPath)) throw new Exception("File o root non validi."); string relativePath = allPath.Replace(syncroPath, "/"); //controlli if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(accesstoken)) { throw new Exception("Lo username o l'accesstoken non può essere nullo."); } if (!relativePath.StartsWith("/")) { throw new Exception("Il path deve iniziare con uno slash."); } if (!File.Exists(allPath)) { throw new Exception("Il path assoluto non esiste in questo file system"); } string name = Path.GetFileName(relativePath); string path = Path.GetDirectoryName(relativePath).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; DateTime lastUpdate = File.GetLastWriteTime(allPath); FileInfo file = new FileInfo(allPath); long dim = file.Length; string hashFile = UserUtils.GetFileHash(allPath); if (String.IsNullOrEmpty(name) || name.StartsWith("\\")) { throw new Exception("Il nome del file non può essere nullo e non può iniziare con uno slash."); } if (String.IsNullOrEmpty(hashFile)) { throw new Exception("L'hash del file non può essere vuoto o nullo."); } if (dim < 0) { throw new Exception("La dimensione del file non può essere negativa."); } long nchunkUser = (long)Math.Ceiling(((double)file.Length / (double)ProtocolStrings.CHUNK_SIZE)); if (nchunkUser < 0) { throw new Exception("Il numero di chunk del file non può essere negativo."); } //la lista di chunk da inviare al server List<VChunk> listChunk = new List<VChunk>(); PushFile_D pD = null; //primo controllo da fare if (this.Stopped) return false; //apro il file e calcolo il numero di chunk using (FileStream fs = new FileStream(allPath, FileMode.Open, FileAccess.Read, FileShare.Delete)) { byte[] c = new byte[ProtocolStrings.CHUNK_SIZE]; string hashChunk; VChunk vc = null; for (long i = 0; i < nchunkUser; i++) { if (this.Stopped) return false; int l = fs.Read(c, 0, c.Count()); hashChunk = UserUtils.GetHash(c, l); vc = new VChunk(i, -1, -1, hashChunk, l, lastUpdate); listChunk.Add(vc); } if (this.Stopped) return false; //invio la richiesta PushFile_A req = new PushFile_A(username, accesstoken, subcmd, name, path, lastUpdate, hashFile, dim, nchunkUser, client_vfsversion, listChunk); UserUtils.NetworkWriteLine(sw, req.ToJSON()); //attendo la risposta del server string rec = UserUtils.NetworkReadLine(sr); Response r = Response.fromJson(rec); if (r.Result == 401) { //problemi con la login T (token) throw new UserLoginException(BadLoginResponse.fromJson(rec).Msg); } else if (r.Result != 200) { throw new Exception(BadPushFileResponse.fromJson(rec).Msg); } if (this.Stopped) return false; PushFile_Abis pAbis = PushFile_Abis.fromJson(rec); //ora il server mi deve mandare la lista di chunk che gli devo inviare //N.B. se la lista è vuota devo andare direttamente all'ultima operazione. if (pAbis.NChunk > 0) { PushFile_B pB = null; byte[] filledChunk = new byte[ProtocolStrings.CHUNK_SIZE]; long id = -1; fs.Seek(0, SeekOrigin.Begin); do { if (this.Stopped) return false; pB = PushFile_B.fromJson(UserUtils.NetworkReadLine(sr)); id = pB.IdChunk; fs.Seek(id * (ProtocolStrings.CHUNK_SIZE), SeekOrigin.Begin); int l = fs.Read(filledChunk, 0, (int)ProtocolStrings.CHUNK_SIZE); //TODO con l'ultimo chunk sprechiamo byte(inviamo byte inutili). PushFile_C pC = new PushFile_C(filledChunk, l); UserUtils.NetworkWriteLine(sw, pC.toJSON()); } while (!pB.Done); } pD = PushFile_D.fromJson(UserUtils.NetworkReadLine(sr)); /** N.B. da qui in poi non dobbiamo più stoppare il thread perché il server ha committato **/ } //ora devo andare a salvare il synctoken (che poi sarebbe il vfsversion) SyncToken.Instance.VFSVersion = pD.NewVfsversion; } catch (SocketException e) { throw new NetworkException("Si è verificato un errore di rete.", e); } catch (UserLoginException e) { throw e; } catch (IOException e) { if ((uint)e.HResult == FILE_IN_USE) { throw new FileInUseException("Il file è in uso da un altro processo, verrà gestito più tardi."); } } catch (Exception e) { throw new PushFileExceptionU(e.Message); } /**************************************** Fine della funzione ***************************************/ } } return true; }
public void HandlePushFile(PushFile_A req, StreamReader sr, StreamWriter sw, ConnectionHandler connHandler) { // Controllo degli argomenti if (String.IsNullOrEmpty(req.Name)) throw new PushFileException("Il nome del file è invalido."); if (String.IsNullOrEmpty(req.Path)) throw new PushFileException("Il path del file invalido."); if (req.Name.StartsWith("/")) throw new PushFileException("Il nome del file non può iniziare per slash."); if (!req.Path.StartsWith("\\") || !req.Path.EndsWith("\\")) throw new PushFileException("Il Path deve iniziare con slash e deve terminare con un backslash."); if (req.Name.Contains("/") || req.Name.Contains("\\")) throw new PushFileException("Carattere non valido nel nome del file."); lock (this) { try { // Controlla che lo user sia sincronizzato. SQLiteConnection conn; string dir; string db = UserManager.Instance.GetUserDbPath(this.Id, out dir); using (conn = new SQLiteConnection(String.Format("Data Source={0};Version=3;", db))) { conn.Open(); using (SQLiteTransaction tr = conn.BeginTransaction()) { try { using (SQLiteCommand cmd = conn.CreateCommand()) { PushFile_Abis pAbis = null; PushFile_B pB = null; PushFile_C pC = null; IEnumerable<VChunk> neededChunks = null; long vfsversion = -1; long version = -1; long fid = -1; // Gestione richiesta PushFileA try { // Controlla che lo user sia allineato in termini di versione del vfs cmd.CommandText = "SELECT ifnull(max(vfsversion),-1) FROM files WHERE iduser = $iduser"; cmd.Parameters.AddWithValue("iduser", this.Id); long currver = (long)cmd.ExecuteScalar(); //se non è allineato, sollevare un'eccezzione di tipo needed sync cmd.Parameters.Clear(); if (currver > req.ClientVfsVersion) throw new NotSynchedException("La versione del client " + req.ClientVfsVersion + " non corrisponde all'ultima del server " + currver + ", perciò il push di qualsiasi file è disabilitato."); // Se lo user è allineato controlla se il file è nuovo, ovvero se esiste già un file con medesimo path e nome cmd.CommandText = "SELECT ifnull(id,-1) " + "FROM files " + "WHERE name = $name AND path=$path AND iduser=$iduser "; cmd.Parameters.AddWithValue("name", req.Name); cmd.Parameters.AddWithValue("path", req.Path); cmd.Parameters.AddWithValue("iduser", this.Id); bool exists = false; using (SQLiteDataReader r = cmd.ExecuteReader()) { if (r.Read()) { fid = r.GetInt64(0); exists = true; } else { //qui devo discriminare un file spostato, da un file rinominato, da un file creato a partire da una copia già esistente. fid = -1; exists = false; } } bool delete = req.SubCmd == ProtocolStrings.SUBCMD_DELETE; if (delete && (req.Nchunk > 0 || req.Dimension > 0)) throw new PushFileException("Incoerenza: si è specificato il subcmd di tipo deleted ma nchunk o dimensione del file sono maggiori di 0."); // Se il file non esisteva, procedo all'inserimento //N.B. cado in questo caso anche quando sposto o rinomino un file if (!exists) { // Non si possono eliminare file inesistenti :) if (delete) throw new FileNotFoundException("Non è possibile eliminare un file che non esiste!"); // Non posso inserire un file contenuto in una cartella se la cartella non esiste. // TODO // Devo selezionare la cartella parent relativa al file che voglio pushare // Se la cartella esiste, return true -> posso andare avanti // Altrimenti throw new exception(). cmd.Parameters.Clear(); cmd.CommandText = "SELECT path from directories WHERE path=$path AND iduser=$iduser "; cmd.Parameters.AddWithValue("path",req.Path); cmd.Parameters.AddWithValue("iduser", this.Id); using (SQLiteDataReader r = cmd.ExecuteReader()) { if (!r.Read()) throw new PushFileException("Il file "+req.Name+" non può essere inserito perchè non è stata ancora inserita la cartella parent a cui appartiene: "+req.Path); } // Battezzo un fileid per l'inserimento cmd.CommandText = "SELECT ifnull(max(id),-1), ifnull(max(vfsversion),-1) " + "FROM files " + "WHERE iduser=$iduser "; cmd.Parameters.AddWithValue("iduser", this.Id); using (SQLiteDataReader r = cmd.ExecuteReader()) { if (!r.Read()) throw new PushFileException("Errore durante query nel db"); fid = r.GetInt64(0); vfsversion = r.GetInt64(1); } fid++; } else { // Se il file esiste già, ne recupero l'ultima versione cmd.CommandText = "SELECT max(version), max(vfsversion) " + "FROM files " + "WHERE iduser=$iduser AND id=$id"; cmd.Parameters.AddWithValue("iduser", this.Id); cmd.Parameters.AddWithValue("id", fid); using (SQLiteDataReader r = cmd.ExecuteReader()) { if (!r.Read()) throw new PushFileException("Errore durante query nel db"); version = r.GetInt64(0); } cmd.Parameters.Clear(); cmd.CommandText = "SELECT max(vfsversion) " + "FROM files " + "WHERE iduser=$iduser"; cmd.Parameters.AddWithValue("iduser", this.Id); using (SQLiteDataReader r = cmd.ExecuteReader()) { if (!r.Read()) throw new PushFileException("Errore durante query nel db"); vfsversion = r.GetInt64(0); } } //Se il file è nuovo, neededChunks conterrà tutti i chunk che formano il nuovo file neededChunks = exists ? GetNeededChunks(fid, version, req) : req.ListChunk; /* L'utente si PUO' FARE I FOTTUTI CAZZI SUA. //TODO //Se il file che il client mi ha mandato è esattamente lo stesso file che il server aveva già, lancia un'eccezzione! if (neededChunks.Count() == 0) { // Controllo cmd.Parameters.Clear(); cmd.CommandText = "SELECT nchunk, name, path, dimension, lastupdate, hash " + "FROM files " + "WHERE id=$id AND version=$version AND iduser=$iduser"; cmd.Parameters.AddWithValue("id", fid); cmd.Parameters.AddWithValue("version", version); cmd.Parameters.AddWithValue("iduser", this.Id); using (SQLiteDataReader r = cmd.ExecuteReader()) { // Non dovrebbe mai accadere if (r.Read()) { // Solo se il file esisteva, controlla se i metadati sono cambiati. long nchunks = r.GetInt64(0); string name = r.GetString(1); string path = r.GetString(2); long dimension = r.GetInt64(3); DateTime dt = DateTime.Parse(r.GetString(4)); string hash = r.GetString(5); // Se nessuna di queste informazioni è cambiata, non ha senso continuare nell'inserimento! Il file è uguale! if (nchunks == req.Nchunk && name == req.Name && path == req.Path && dimension == req.Dimension && dt == req.LastUpdate && hash == req.Hash) throw new PushFileException("Si sta tentando di inserire un file identico a quello precedente."); } } } */ string deletedhash = null; // Se è stata richiesta una eliminazione, devo recuperare l'ultimo hash del file ed inserire quello nel record // del file eliminato if (delete) { cmd.Parameters.Clear(); cmd.CommandText = "SELECT hash " + "FROM files " + "WHERE id=$id AND version=$version AND iduser=$iduser"; cmd.Parameters.AddWithValue("id", fid); cmd.Parameters.AddWithValue("version", version); cmd.Parameters.AddWithValue("iduser", this.Id); deletedhash = (string)cmd.ExecuteScalar(); } // In ogni caso incremento la versione. version++; vfsversion++; // A questo punto ho il fileid, la versione del file e la vfsversion popolate. // Controllo la coerenza della richiesta: il numero di chunk deve essere compatibile con il nostro sistema immgazzinamento. if (req.Nchunk != Math.Ceiling((double)((double)req.Dimension / (double)ProtocolStrings.CHUNK_SIZE))) { throw new PushFileException("Numero di chunk ricevuti incoerente!"); } // Procedo con l'inserimento nella tabella files cmd.Parameters.Clear(); cmd.CommandText = "INSERT INTO files(id, version, vfsversion, nchunk, name, path, dimension, lastupdate, deleted, hash, iduser) " + "VALUES($id,$version,$vfsversion, $nchunk, $name, $path, $dimension, $dt, $deleted, $hash, $iduser) "; cmd.Parameters.AddWithValue("id", fid); cmd.Parameters.AddWithValue("version", version); cmd.Parameters.AddWithValue("vfsversion", vfsversion); cmd.Parameters.AddWithValue("nchunk", delete ? 0 : req.Nchunk); //size/nchunk cmd.Parameters.AddWithValue("name", req.Name); cmd.Parameters.AddWithValue("path", req.Path); cmd.Parameters.AddWithValue("dimension", req.Dimension); cmd.Parameters.AddWithValue("dt", req.LastUpdate); cmd.Parameters.AddWithValue("deleted", delete ? 1 : 0); cmd.Parameters.AddWithValue("hash", delete ? deletedhash : req.Hash); cmd.Parameters.AddWithValue("iduser", this.Id); if (cmd.ExecuteNonQuery() != 1) { throw new PushFileException("Errore durante query nel db"); } //Setto il timeout sullo stream reader e writer perché il send e receive di messaggi //tra client e server potrebbe fallire //TODO ripristinare dopo test sr.BaseStream.ReadTimeout = (int)Properties.Settings.Default.READER_TIMEOUT; sw.BaseStream.WriteTimeout = (int)Properties.Settings.Default.WRITER_TIMEOUT; //TODO pAbis = new PushFile_Abis(String.Format("Sono necessari {0} chunks per quest operazione.", neededChunks.Count()), neededChunks.Count()); sw.WriteLine(pAbis.toJSON()); sw.Flush(); } catch (FileNotFoundException e) { // Se ho ricevuto un qualsiasi errore, mando un esito negativo // che a questo livello del protocollo si traduce con una risposta PushFile_Abis pAbis = new PushFile_Abis(404,"Il client ha richiesto l'eliminazione di un file non presente sul server",-1); sw.WriteLine(pAbis.toJSON()); sw.Flush(); } catch (Exception e) { // Se ho ricevuto un qualsiasi errore, mando un esito negativo // che a questo livello del protocollo si traduce con una risposta PushFile_Abis pAbis = new PushFile_Abis("Si è verificato un errore durante la gestione della prima parte del protocollo di push: " + e.Message, -1, false); sw.WriteLine(pAbis.toJSON()); sw.Flush(); // A questo punto rilancia al chiamante. throw e; } // TODO: eventuale gestione di pushfileC/D in caso di errore. Non riesco a vedere immediate situazioni di anomalia, quindi // per il momento posticipiamo queste gestioni. for (int i = 0; i < neededChunks.Count(); i++) { VChunk chnk = neededChunks.ElementAt(i); // Invio una richiesta di trasferimento chunk pB = new PushFile_B("Mandami il chunk " + i, chnk.Id, i == neededChunks.Count() - 1); // Nota, I == ID. sw.WriteLine(pB.toJSON()); sw.Flush(); // Leggo la risposta dal client string str = sr.ReadLine(); pC = PushFile_C.fromJson(str); string hashchunk = GetHash(pC.Data,(int)pC.Length); if (hashchunk != chnk.Hash) { throw new PushFileException("L'hash ricevuto non è conforme a quello appena calcolato"); } if (chnk.Dim != pC.Length) throw new PushFileException("Dimensioni incoerenti tra PushA e PushC"); cmd.Parameters.Clear(); cmd.CommandText = "INSERT INTO chunks(id, idfile, version, hash, dimension, lastupdate, data) " + "VALUES($id,$idfile,$version, $hash, $dimension, $lastupdate, $data) "; cmd.Parameters.AddWithValue("id", i); cmd.Parameters.AddWithValue("idfile", fid); cmd.Parameters.AddWithValue("version", version); //prima versione del file cmd.Parameters.AddWithValue("hash", hashchunk); cmd.Parameters.AddWithValue("dimension", pC.Length); cmd.Parameters.AddWithValue("lastupdate", chnk.LastUpdate); cmd.Parameters.AddWithValue("data", pC.Data); if (cmd.ExecuteNonQuery() != 1) { throw new PushFileException("Errore durante query nel db"); } } //abbiamo ricevuto tutti i chunk PushFile_D pD = new PushFile_D("File sincronizzato. Invio il nuovo vfsVersion.", vfsversion); sw.WriteLine(pD.toJSON()); sw.Flush(); // Se tutto è andato bene, committa la transazione. tr.Commit(); _nCounter++; Monitor.PulseAll(this); } } catch (Exception e) { // ROLLBACK THE ENTIRE WORLD tr.Rollback(); throw e; } finally { //Resetto i valori di timeout all'infinito. sr.BaseStream.ReadTimeout = sr.BaseStream.ReadTimeout = System.Threading.Timeout.Infinite; sw.BaseStream.WriteTimeout = sr.BaseStream.ReadTimeout = System.Threading.Timeout.Infinite; } } } } catch (Exception e) { throw new PushFileException(e.Message); } } }
public static long DoPushFile(string username, string accesstoken, string allPath, string subcmd, long client_vfsversion, StreamWriter sw, StreamReader sr) { try { string syncroPath = GetRootPath(); if (!allPath.StartsWith(syncroPath)) throw new Exception("File o root non validi."); string relativePath = allPath.Replace(syncroPath, "/"); //controlli if(String.IsNullOrEmpty(username) || String.IsNullOrEmpty(accesstoken)){ throw new Exception("Lo username o l'accesstoken non può essere nullo."); } if (!relativePath.StartsWith("/")) { throw new Exception("Il path deve iniziare con uno slash."); } File.Exists(allPath); string name = Path.GetFileName(relativePath); string path = Path.GetDirectoryName(relativePath); path = path.TrimEnd(Path.DirectorySeparatorChar); path += Path.DirectorySeparatorChar; DateTime lastUpdate = File.GetLastWriteTime(allPath); FileInfo file = new FileInfo(allPath); long dim = file.Length; string hashFile = GetFileHash(allPath); if(String.IsNullOrEmpty(name) || name.StartsWith("\\")){ throw new Exception("Il nome del file non può essere nullo e non può iniziare con uno slash."); } if(String.IsNullOrEmpty(hashFile)){ throw new Exception("L'hash del file non può essere vuoto o nullo."); } if(dim<0){ throw new Exception("La dimensione del file non può essere negativa."); } FileInfo finfo = new FileInfo(allPath); long nchunkUser = (long)Math.Ceiling(((double)finfo.Length / (double)ProtocolStrings.CHUNK_SIZE)); if(nchunkUser<0){ throw new Exception("Il numero di chunk del file non può essere negativo."); } //la lista di chunk da inviare al server List<VChunk> listChunk = new List<VChunk>(); PushFile_D pD = null; //apro il file e calcolo il numero di chunk using (FileStream fs = File.OpenRead(allPath)) { byte[] c = new byte[ProtocolStrings.CHUNK_SIZE]; string hashChunk; VChunk vc = null; for (long i = 0; i < nchunkUser; i++) { int l = fs.Read(c, 0, c.Count()); hashChunk = GetHash(c,l); vc = new VChunk(i, -1, -1, hashChunk, l, lastUpdate); listChunk.Add(vc); } //invio la richiesta PushFile_A req = new PushFile_A(username, accesstoken, subcmd, name, path, lastUpdate, hashFile, dim, nchunkUser, client_vfsversion, listChunk); NetworkWriteLine(sw, req.ToJSON()); //attendo la risposta del server string rec = NetworkReadLine(sr); Response r = Response.fromJson(rec); if (r.Result == 401) { //problemi con la login T (token) throw new UserLoginException(BadLoginResponse.fromJson(rec).Msg); } else if(r.Result != 200){ throw new Exception(BadPushFileResponse.fromJson(rec).Msg); } PushFile_Abis pAbis = PushFile_Abis.fromJson(rec); //ora il server mi deve mandare la lista di chunk che gli devo inviare //N.B. se la lista è vuota devo andare direttamente all'ultima operazione. if (pAbis.NChunk > 0) { PushFile_B pB = null; byte[] filledChunk = new byte[ProtocolStrings.CHUNK_SIZE]; long id = -1; fs.Seek(0, SeekOrigin.Begin); do { pB = PushFile_B.fromJson(NetworkReadLine(sr)); id = pB.IdChunk; fs.Seek(id * (ProtocolStrings.CHUNK_SIZE), SeekOrigin.Begin); int l = fs.Read(filledChunk, 0, (int)ProtocolStrings.CHUNK_SIZE); //TODO con l'ultimo chunk sprechiamo byte(inviamo byte inutili). PushFile_C pC = new PushFile_C(filledChunk, l); NetworkWriteLine(sw, pC.toJSON()); } while (!pB.Done); } pD = PushFile_D.fromJson(NetworkReadLine(sr)); } //ora devo andare a salvare il synctoken (che poi sarebbe il vfsversion) return pD.NewVfsversion; } catch (NetworkException e) { throw e; } catch (UserLoginException e) { throw e; } catch (Exception e) { throw new PushFileExceptionU(e.Message); } }
private IEnumerable<VChunk> GetNeededChunks(long fid, long fversion, PushFile_A req) { // NOTA BENE: Il lock deve essere acquisito dal chiamante. // Recupero tutti i chunks dell'ultima versione del file IEnumerable<VChunk> all = GetFileChunks(fid, fversion); List<VChunk> res = new List<VChunk>(); if (req.ListChunk.Count() > all.Count()) { // Il client ha esteso il file // Filtro solo quelli che sono cambiati for (int i = 0; i < req.ListChunk.Count() && i<all.Count(); i++) { if (all.ElementAt(i).Hash.CompareTo(req.ListChunk.ElementAt(i).Hash) != 0) res.Add(req.ListChunk.ElementAt(i)); } // Aggiungo i restanti for (int i = all.Count(); i < req.ListChunk.Count(); i++) res.Add(req.ListChunk.ElementAt(i)); } else { // Il client ha ridotto il file // Filtro solo quelli che sono cambiati for (int i = 0; i < req.ListChunk.Count(); i++) { if (all.ElementAt(i).Hash.CompareTo(req.ListChunk.ElementAt(i).Hash) != 0) res.Add(req.ListChunk.ElementAt(i)); } } return res; }