file.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. package model
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "fmt"
  6. "io"
  7. "mime"
  8. "os"
  9. "path/filepath"
  10. "time"
  11. )
  12. // File represents a file to be shared or received
  13. type File struct {
  14. ID string
  15. Name string
  16. Path string
  17. Size int64
  18. Type string
  19. SHA256 string
  20. LastModified int64
  21. }
  22. // NewFile creates a File instance from a file path
  23. func NewFile(path string) (*File, error) {
  24. info, err := os.Stat(path)
  25. if err != nil {
  26. return nil, err
  27. }
  28. fileType := determineFileType(path)
  29. file := &File{
  30. ID: generateID(path),
  31. Name: filepath.Base(path),
  32. Path: path,
  33. Size: info.Size(),
  34. Type: fileType,
  35. LastModified: info.ModTime().UnixMilli(),
  36. }
  37. // Calculate SHA-256 hash asynchronously for large files
  38. if file.Size < 50*1024*1024 { // Only pre-calculate for files < 50MB
  39. hash, err := calculateSHA256(path)
  40. if err == nil {
  41. file.SHA256 = hash
  42. }
  43. }
  44. return file, nil
  45. }
  46. // ToFileDto converts a File to a FileDto for API responses
  47. func (f *File) ToFileDto() FileDto {
  48. var sha256Ptr *string
  49. if f.SHA256 != "" {
  50. sha256Ptr = &f.SHA256 // Assign address if SHA256 is calculated
  51. }
  52. modTime := time.Unix(0, f.LastModified).Format(time.RFC3339)
  53. return FileDto{
  54. ID: f.ID,
  55. FileName: f.Name,
  56. Size: f.Size,
  57. FileType: f.Type,
  58. SHA256: sha256Ptr,
  59. Metadata: &FileMetadata{
  60. Modified: &modTime,
  61. },
  62. }
  63. }
  64. // generateID creates a unique ID for a file based on path and timestamp
  65. func generateID(path string) string {
  66. timestamp := time.Now().UnixNano()
  67. data := []byte(fmt.Sprintf("%s-%d", path, timestamp))
  68. hash := sha256.Sum256(data)
  69. return hex.EncodeToString(hash[:8]) // Use first 8 bytes for a shorter ID
  70. }
  71. // determineFileType attempts to determine the file type from extension or content
  72. func determineFileType(path string) string {
  73. ext := filepath.Ext(path)
  74. if mimeType := mime.TypeByExtension(ext); mimeType != "" {
  75. return mimeType
  76. }
  77. // Fallbacks if mime package doesn't know the extension
  78. switch ext {
  79. case ".md":
  80. return "text/markdown"
  81. case ".apk":
  82. return "application/vnd.android.package-archive"
  83. default:
  84. return "application/octet-stream"
  85. }
  86. }
  87. // calculateSHA256 calculates the SHA-256 hash of a file
  88. func calculateSHA256(path string) (string, error) {
  89. f, err := os.Open(path)
  90. if err != nil {
  91. return "", err
  92. }
  93. defer f.Close()
  94. h := sha256.New()
  95. if _, err := io.Copy(h, f); err != nil {
  96. return "", err
  97. }
  98. return hex.EncodeToString(h.Sum(nil)), nil
  99. }