list = collect(); $this->packets = collect(); $this->sending = NULL; } public function __get($key) { switch ($key) { case 'dbids': return $this->sending->messages->pluck('echoarea','dbid'); case 'fd': return is_resource($this->f) ?: $this->f; case 'files_count': return $this->list ->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); }) ->count(); case 'files_size': return $this->list ->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); }) ->sum(function($item) { return $item->size; }); case 'filepos': return $this->file_pos; case 'mail_count': return $this->list ->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); }) ->count() + $this->packets->count(); case 'mail_size': return $this->list ->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); }) ->sum(function($item) { return $item->size; }) + $this->packets->sum(function($item) { return $item->size; }); case 'sendas': return $this->sending?->{$key}; // The mtime is the time of the youngest message in the packet for the sending packet case 'mtime': return $this->sending?->youngest()->timestamp; // The name is derived from the youngest message in the packet case 'name': return sprintf('%08x',timew($this->sending?->youngest())); case 'size': return strlen($this->sending?->file); case 'total_sent': return $this->list ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) ->count() + $this->packets ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) ->count(); case 'total_sent_bytes': return $this->list ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) ->sum(function($item) { return $item->size; }) + $this->packets ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; }) ->sum(function($item) { return $item->size; }); case 'total_count': return $this->list ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; }) ->count() + $this->packets ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; }) ->count(); case 'total_size': return $this->list ->sum(function($item) { return $item->size; }) + $this->packets->sum(function($item) { return $item->size; }); default: throw new Exception('Unknown key: '.$key); } } /** * Add a file to the list of files to send * * @param string $file * @throws Exception * @todo Catch if we add the same file twice */ public function add(string $file): void { Log::debug(sprintf('%s:+ add [%s]',self::LOGKEY,$file)); try { $this->list->push(new Item($file,self::I_SEND)); } catch (FileNotFoundException) { Log::error(sprintf('%s:! Item [%s] doesnt exist',self::LOGKEY,$file)); return; } catch (UnreadableFileEncountered) { Log::error(sprintf('%s:! Item [%s] cannot be read',self::LOGKEY,$file)); return; // Uncaught, rethrow the error } catch (Exception $e) { throw new Exception($e->getMessage()); } } /* private function compress(string $comp_mode): void { switch ($comp_mode) { case 'BZ2': $this->comp_data = bzcompress($buf); break; case 'GZ': $this->comp_data = gzcompress($buf); break; } } */ /** * Close the file descriptor of the file we are sending * * @param bool $successful * @throws Exception */ public function close(bool $successful): void { if (! $this->f) throw new Exception('No file to close'); if ($successful) { $this->sending->sent = TRUE; $end = time()-$this->start; Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',self::LOGKEY,$this->sending->name,$end)); } // @todo This should be done better isType == file? if ((! $this->sending instanceof Mail) && (! $this->sending->isType(self::IS_TIC))) fclose($this->f); $this->sending = NULL; $this->file_pos = 0; $this->f = NULL; } /** * Check if we are at the end of the file * * @return bool */ public function feof(): bool { return (($this->sending instanceof Mail) || ($this->sending->isType(self::IS_TIC))) ? ($this->file_pos === $this->size) : feof($this->f); } /** * Add our mail to the send queue * * @param Address $ao * @return bool * @throws Exception * @todo We need to make this into a transaction, incase the transfer fails. */ public function files(Address $ao): bool { $file = FALSE; // If the node is marked as hold - dont send any files. if ($ao->system->hold) { Log::info(sprintf('%s: - System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id)); return FALSE; } // Files if (($x=$ao->getFiles())->count()) { Log::debug(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn)); // Add Files foreach ($x as $fo) { $this->list->push(new Item($fo,self::I_SEND)); $this->list->push(new Tic($ao,$fo,self::I_SEND)); } $file = TRUE; } return $file; } /** * Open a file for sending * * @param string $compress * @return bool * @throws Exception */ public function open(string $compress=''): bool { Log::debug(sprintf('%s:+ Opening file to send',self::LOGKEY)); // If we have mail, we'll send that first if ($this->sending = $this->packets ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; }) ->first()) { Log::debug(sprintf('%s:- Sending [%s]',self::LOGKEY,$this->sending->name)); $this->file_pos = 0; $this->start = time(); $this->f = TRUE; /* if ($compress) $this->comp_data = $this->compdata($compress); */ return TRUE; } $this->sending = $this->list ->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; }) ->first(); if (! $this->sending) throw new Exception('No files to open'); $this->file_pos = 0; $this->start = time(); // If sending->file is a string, then we dont need to actually open anything if ($this->sending->isType(self::IS_TIC)) { $this->f = TRUE; return TRUE; } // If sending file is a File::class, then our file is s3 if (! $this->sending->name && $this->sending->filemodel) { $this->f = Storage::readStream($this->sending->filemodel->full_storage_path); return TRUE; } else { $this->f = fopen($this->sending->name,'rb'); if (! $this->f) { Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->sending->name)); return FALSE; } Log::info(sprintf('%s:= open - File [%s] opened with size [%d]',self::LOGKEY,$this->sending->name,$this->sending->size)); return TRUE; } } /** * Add our mail to the send queue * * @param Address $ao * @return bool * @throws Exception * @todo We need to make this into a transaction, incase the transfer fails. */ public function mail(Address $ao,bool $update=TRUE): bool { $mail = FALSE; // If the node is marked as hold - dont send any mail. if ($ao->system->hold) { Log::info(sprintf('%s: - System [%d] is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id)); return FALSE; } // Netmail if ($x=$ao->getNetmail($update)) { Log::debug(sprintf('%s: - Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn)); $this->packets->push(new Mail($x,self::I_SEND)); $mail = TRUE; } // Echomail if ($x=$ao->getEchomail($update)) { Log::debug(sprintf('%s: - Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn)); $this->packets->push(new Mail($x,self::I_SEND)); $mail = TRUE; } return $mail; } /** * Read bytes of the sending file * * @param int $length * @return string|null * @throws UnreadableFileEncountered * @throws Exception */ public function read(int $length): ?string { if (! $this->f) throw new Exception('No file open for read'); // We are sending mail if ($this->sending instanceof Mail) { $data = $this->sending->read($this->file_pos,$length); // We are sending a tic file } else if ($this->sending->isType(self::IS_TIC)) { $data = $this->sending->read($this->file_pos,$length); } else { $data = fread($this->f,$length); } $this->file_pos += strlen($data); if ($this->DEBUG) Log::debug(sprintf('%s: - Read [%d] bytes, file pos now [%d]',self::LOGKEY,strlen($data),$this->file_pos)); if ($data === FALSE) throw new UnreadableFileEncountered('Error reading file: '.$this->sending->name); return $data; } /** * Seek to a specific position of our file * * @param int $pos * @return bool * @throws Exception */ public function seek(int $pos): bool { if (! $this->f) throw new Exception('No file open for seek'); if (($this->sending instanceof Mail) || $this->sending->isType(self::IS_TIC)) { $pos = ($pos < $this->size) ? $pos : $this->size; $rc = TRUE; } else { $rc = (fseek($this->f,$pos,SEEK_SET) === 0); } if ($rc) $this->file_pos = $pos; Log::debug(sprintf('%s:= Seeked to [%d]',self::LOGKEY,$this->file_pos)); return $rc; } }