github.com/swishcloud/filesync@v0.0.0-20231002120458-6ade2feed6f9/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"crypto/tls"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"log"
    10  	"net"
    11  	"net/http"
    12  	"net/url"
    13  	"os"
    14  	"strconv"
    15  
    16  	"github.com/google/uuid"
    17  	"github.com/swishcloud/filesync/internal"
    18  	"github.com/swishcloud/filesync/storage"
    19  
    20  	"golang.org/x/oauth2"
    21  	"gopkg.in/yaml.v2"
    22  
    23  	"github.com/swishcloud/filesync/message"
    24  	"github.com/swishcloud/filesync/message/models"
    25  	"github.com/swishcloud/filesync/session"
    26  	"github.com/swishcloud/filesync/x"
    27  	"github.com/swishcloud/gostudy/common"
    28  )
    29  
    30  type FileSyncServer struct {
    31  	config          *config
    32  	skip_tls_verify bool
    33  	httpClient      *http.Client
    34  	storage         *storage.SQLITEManager
    35  	clients         []*client
    36  	connect         chan *session.Session
    37  	disconnect      chan *session.Session
    38  }
    39  type client struct {
    40  	session *session.Session
    41  	class   int
    42  }
    43  type config struct {
    44  	IntrospectTokenURL string `yaml:"IntrospectTokenURL"`
    45  	Port               string `yaml:"Port"`
    46  	FileLocation       string `yaml:"FileLocation"`
    47  }
    48  
    49  func (cfg *config) blockDir() string {
    50  	return cfg.FileLocation + "/block/"
    51  }
    52  func (cfg *config) fileDir() string {
    53  	return cfg.FileLocation + "/file/"
    54  }
    55  func (cfg *config) tempDir() string {
    56  	return cfg.FileLocation + "/tmp/"
    57  }
    58  
    59  func NewFileSyncServer(config_file_path string, skip_tls_verify bool) *FileSyncServer {
    60  	s := &FileSyncServer{}
    61  	//s.storage = &storage.SQLITEManager{}
    62  	//s.storage.Initialize()
    63  	s.clients = []*client{}
    64  	s.connect = make(chan *session.Session)
    65  	s.disconnect = make(chan *session.Session)
    66  	s.skip_tls_verify = skip_tls_verify
    67  	s.httpClient = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: skip_tls_verify}}}
    68  	http.DefaultClient = s.httpClient
    69  	s.config = new(config)
    70  	b, err := ioutil.ReadFile(config_file_path)
    71  	if err != nil {
    72  		log.Fatal(err)
    73  	}
    74  	err = yaml.Unmarshal(b, s.config)
    75  	if err != nil {
    76  		log.Fatal(err)
    77  	}
    78  	err = os.MkdirAll(s.config.blockDir(), os.ModePerm)
    79  	if err != nil {
    80  		log.Fatal(err)
    81  	}
    82  	err = os.MkdirAll(s.config.fileDir(), os.ModePerm)
    83  	if err != nil {
    84  		log.Fatal(err)
    85  	}
    86  	err = os.MkdirAll(s.config.tempDir(), os.ModePerm)
    87  	if err != nil {
    88  		log.Fatal(err)
    89  	}
    90  	return s
    91  }
    92  func (s *FileSyncServer) Serve() {
    93  	// Listen on TCP port 2000 on all available unicast and
    94  	// anycast IP addresses of the local system.
    95  	l, err := net.Listen("tcp", ":"+s.config.Port)
    96  	log.Println("accepting tcp connections on port", s.config.Port)
    97  	if err != nil {
    98  		log.Fatal(err)
    99  	}
   100  	defer l.Close()
   101  	// Handle the sessions in a new goroutine.
   102  	go s.serveSessions()
   103  	for {
   104  		// Wait for a connection.
   105  		conn, err := l.Accept()
   106  		if err != nil {
   107  			log.Fatal(err)
   108  		}
   109  		s.connect <- session.NewSession(conn)
   110  	}
   111  }
   112  func checkFile(file models.File, fullPath string) {
   113  	if file.IsHidden {
   114  		err := x.HideFile(fullPath)
   115  		if err != nil {
   116  			panic(err)
   117  		}
   118  	}
   119  }
   120  func (s *FileSyncServer) checkToken(msg *message.Message) (user_id string, token *oauth2.Token, err error) {
   121  	tokenstr := msg.Header[internal.TokenHeaderKey]
   122  	if tokenstr == nil {
   123  		return "", nil, errors.New("token is missing")
   124  	}
   125  	token = &oauth2.Token{AccessToken: tokenstr.(string)}
   126  	rar := common.NewRestApiRequest("GET", s.config.IntrospectTokenURL, nil).SetAuthHeader(token)
   127  	resp, err := internal.RestApiClient().Do(rar)
   128  	if err != nil {
   129  		return "", nil, err
   130  	}
   131  	m, err := common.ReadAsMap(resp.Body)
   132  	if err != nil {
   133  		return "", nil, err
   134  	}
   135  	if m["error"] != nil {
   136  		return "", nil, errors.New(m["error"].(string))
   137  	}
   138  	data := m["data"].(map[string]interface{})
   139  	isActive := data["active"].(bool)
   140  	if !isActive {
   141  		return "", nil, errors.New("the token is not valid")
   142  	}
   143  	sub := data["sub"].(string)
   144  	return sub, token, nil
   145  }
   146  func (s *FileSyncServer) serveSessions() {
   147  	for {
   148  		select {
   149  		case connect := <-s.connect:
   150  			client := &client{session: connect, class: 1}
   151  			s.clients = append(s.clients, client)
   152  			go s.serveClient(client)
   153  		case disconect := <-s.disconnect:
   154  			disconect.Close()
   155  			for index, item := range s.clients {
   156  				if item.session == disconect {
   157  					s.clients = append(s.clients[:index], s.clients[index+1:]...)
   158  					break
   159  				}
   160  			}
   161  		}
   162  	}
   163  }
   164  func (s *FileSyncServer) serveClient(client *client) {
   165  	session := client.session
   166  	defer func() {
   167  		if err := recover(); err != nil {
   168  			log.Println("disconnect session:", session, "cause:", err)
   169  			s.disconnect <- session
   170  		}
   171  	}()
   172  	log.Println("New session:", session)
   173  	for {
   174  		msg, err := session.ReadMessage()
   175  		if err != nil {
   176  			panic(err)
   177  		}
   178  		switch msg.MsgType {
   179  		case message.MT_PING:
   180  			reply_msg := new(message.Message)
   181  			reply_msg.MsgType = message.MT_PANG
   182  			session.SendMessage(reply_msg, nil, 0)
   183  		case message.MT_FILE:
   184  			_, token, err := s.checkToken(msg)
   185  			if err != nil {
   186  				panic(err)
   187  			}
   188  			file_path := s.config.fileDir() + msg.Header["path"].(string)
   189  			md5 := msg.Header["md5"].(string)
   190  			file_size := int64(msg.Header["file_size"].(float64))
   191  			uploaded_size := int64(msg.Header["uploaded_size"].(float64))
   192  			server_file_id := msg.Header["server_file_id"].(string)
   193  			block_name := uuid.New().String()
   194  			block_path := s.config.blockDir() + block_name
   195  			f, err := os.Create(block_path)
   196  			if err != nil {
   197  				panic(err)
   198  			}
   199  			written, err := io.CopyN(f, session, msg.BodySize)
   200  			if err != nil {
   201  				log.Println("receive file block failed:", err)
   202  			} else {
   203  				log.Println("received a new file block")
   204  			}
   205  			start := uploaded_size
   206  			end := written + start
   207  			//record uploaded file block
   208  			parameters := url.Values{}
   209  			parameters.Add("server_file_id", server_file_id)
   210  			parameters.Add("name", block_name)
   211  			parameters.Add("start", strconv.FormatInt(start, 10))
   212  			parameters.Add("end", strconv.FormatInt(end, 10))
   213  			rar := common.NewRestApiRequest("POST", internal.GetApiUrlPath("file-block"), []byte(parameters.Encode())).SetAuthHeader(token)
   214  			_, err = internal.RestApiClient().Do(rar)
   215  			if err != nil {
   216  				panic(err)
   217  			}
   218  			//assemble files if bytes of whole file has uploaded
   219  			if end == file_size {
   220  				//query all file blocks
   221  				rar := common.NewRestApiRequest("GET", internal.GetApiUrlPath("file-block")+"?server_file_id="+server_file_id, nil).SetAuthHeader(token)
   222  				resp, err := internal.RestApiClient().Do(rar)
   223  				if err != nil {
   224  					panic(err)
   225  				}
   226  				m, err := common.ReadAsMap(resp.Body)
   227  				if err != nil {
   228  					panic(err)
   229  				}
   230  				data := m["data"].([]interface{})
   231  				//create temp file
   232  				temp_file_path := s.config.tempDir() + uuid.New().String()
   233  				temp_file, err := os.Create(temp_file_path)
   234  				if err != nil {
   235  					panic(err)
   236  				}
   237  				for i := len(data) - 1; i >= 0; i-- {
   238  					block := data[i].(map[string]interface{})
   239  					log.Println(block)
   240  					block_path := block["Path"].(string)
   241  					block_file, err := os.Open(s.config.blockDir() + block_path)
   242  					if err != nil {
   243  						panic(err)
   244  					}
   245  					//copy block content to temp file
   246  					_, err = io.Copy(temp_file, block_file)
   247  					if err != nil {
   248  						panic(err)
   249  					}
   250  					block_file.Close()
   251  				}
   252  				//close file stream
   253  				temp_file.Close()
   254  				//check md5
   255  				tmp_file_md5, err := x.Hash_file_md5(temp_file_path)
   256  				if err != nil {
   257  					panic(err)
   258  				}
   259  				if tmp_file_md5 != md5 {
   260  					panic("the md5 is inconsistent")
   261  				}
   262  				//already assembled successfully, then change temp file name to final file name
   263  				err = os.Rename(temp_file_path, file_path)
   264  				if err != nil {
   265  					panic(err)
   266  				}
   267  				//change status
   268  				rar = common.NewRestApiRequest("PUT", internal.GetApiUrlPath("file"), []byte(fmt.Sprintf("server_file_id=%s", server_file_id))).SetAuthHeader(token)
   269  				_, err = internal.RestApiClient().Do(rar)
   270  				if err != nil {
   271  					panic(err)
   272  				}
   273  			}
   274  			reply := message.NewMessage(message.MT_Reply)
   275  			session.Send(reply, nil)
   276  		case message.MT_Request_Repeat:
   277  		case message.MT_Download_File:
   278  			file_path := s.config.fileDir() + msg.Header["path"].(string)
   279  			err := session.SendFile(file_path, func(filename string, md5 string, size int64) (int64, bool) {
   280  				return 0, true
   281  			})
   282  			if err != nil {
   283  				panic(err)
   284  			}
   285  		case message.MT_DISCONNECT:
   286  			panic(errors.New("peer requested to disconnect connections"))
   287  		case message.MT_SYNC:
   288  
   289  		}
   290  	}
   291  }