1+ import functools
12import operator
2- from typing import Callable , List , TypeVar , Any , Optional
3+ from typing import Callable , Dict , List , TypeVar , Any , Optional , cast
34
45from mcdreforged .api .all import CommandSource , RTextBase , RText , RTextList , RColor , PermissionLevel
56from typing_extensions import override
1516_R = TypeVar ('_R' )
1617
1718
19+ class _UnixUidGidNameResolver :
20+ def __init__ (self ):
21+ self .__uid_names : Dict [int , Optional [str ]] = {}
22+ self .__gid_names : Dict [int , Optional [str ]] = {}
23+
24+ @functools .cached_property
25+ def supported (self ) -> bool :
26+ try :
27+ import pwd , grp
28+ except ImportError :
29+ return False
30+ else :
31+ return hasattr (pwd , 'getpwuid' ) and hasattr (grp , 'getgrgid' )
32+
33+ def __get_uid_name (self , uid : int ) -> Optional [str ]:
34+ if not self .supported :
35+ return None
36+ if uid not in self .__uid_names :
37+ name = None
38+ try :
39+ import pwd
40+ name = cast (Any , pwd ).getpwuid (uid ).pw_name
41+ except (ImportError , KeyError ):
42+ pass
43+ self .__uid_names [uid ] = name
44+ return self .__uid_names [uid ]
45+
46+ def __get_gid_name (self , gid : int ) -> Optional [str ]:
47+ if not self .supported :
48+ return None
49+ if gid not in self .__gid_names :
50+ name = None
51+ try :
52+ import grp
53+ name = cast (Any , grp ).getgrgid (gid ).gr_name
54+ except (ImportError , KeyError ):
55+ pass
56+ self .__gid_names [gid ] = name
57+ return self .__gid_names [gid ]
58+
59+ @classmethod
60+ def __format_id (cls , id_ : Optional [int ], name_getter : Callable [[int ], Optional [str ]], id_name : str ) -> RTextBase :
61+ if id_ is None :
62+ return RText ('?' , RColor .gray )
63+ if (name := name_getter (id_ )) is not None :
64+ return RText (name , TextColors .number ).h (f'{ id_name } ={ id_ } ' )
65+ return TextComponents .number (id_ )
66+
67+ def uid (self , uid : Optional [int ]) -> RTextBase :
68+ return self .__format_id (uid , self .__get_uid_name , 'uid' )
69+
70+ def gid (self , gid : Optional [int ]) -> RTextBase :
71+ return self .__format_id (gid , self .__get_gid_name , 'gid' )
72+
73+ def pair_columns (self , file : FileInfo ) -> List [RTextBase ]:
74+ if not self .supported :
75+ return []
76+ return [self .uid (file .uid ), self .gid (file .gid )]
77+
78+
1879def _get_raw_size_and_hash_from_blob (blob : BlobInfo ) -> SizeAndHash :
1980 return SizeAndHash (blob .raw_size , blob .hash )
2081
@@ -23,17 +84,40 @@ def _map_or_none(value: Optional[_T], maper: Callable[[_T], _R]) -> Optional[_R]
2384 return maper (value ) if value is not None else None
2485
2586
87+ def _pretty_mode (mode : int ) -> RTextBase :
88+ return TextComponents .file_mode (mode )
89+
90+
91+ def _get_file_size (file : FileInfo ) -> int :
92+ if file .blob is not None :
93+ return file .blob .raw_size
94+ if file .content is not None :
95+ return len (file .content )
96+ return 0
97+
98+
99+ def _signed_file_size (sign : str , size : int , color : RColor ) -> RTextBase :
100+ return RTextList (RText (sign , color ), TextComponents .file_size (size , color = color ))
101+
102+
26103class DiffBackupTask (LightTask [None ]):
27104 def __init__ (self , source : CommandSource , backup_id_old : int , backup_id_new : int ):
28105 super ().__init__ (source )
29106 self .backup_id_old = backup_id_old
30107 self .backup_id_new = backup_id_new
108+ self .__uid_gid_resolver = _UnixUidGidNameResolver ()
31109
32110 @property
33111 @override
34112 def id (self ) -> str :
35113 return 'backup_diff'
36114
115+ def __make_uid_gid_columns (self , file : FileInfo ) -> RTextBase :
116+ columns = self .__uid_gid_resolver .pair_columns (file )
117+ if len (columns ) == 0 :
118+ return RTextList ()
119+ return RTextList (RTextBase .join (' ' , columns ), ' ' )
120+
37121 @override
38122 def run (self ) -> None :
39123 result = DiffBackupAction (self .backup_id_old , self .backup_id_new , compare_status = False ).run ()
@@ -45,6 +129,8 @@ def run(self) -> None:
45129 self .reply_tr ('no_diff' , t_bid_old , t_bid_new )
46130 return
47131
132+ old_diff_size = sum (_get_file_size (file ) for file in result .deleted ) + sum (_get_file_size (file ) for file , _ in result .changed )
133+ new_diff_size = sum (_get_file_size (file ) for file in result .added ) + sum (_get_file_size (file ) for _ , file in result .changed )
48134 self .reply_tr (
49135 'found_diff' ,
50136 TextComponents .number (result .diff_count ),
@@ -53,17 +139,17 @@ def run(self) -> None:
53139 RText (f'+{ len (result .added )} ' , RColor .green ),
54140 RText (f'-{ len (result .deleted )} ' , RColor .red ),
55141 RText (f'*{ len (result .changed )} ' , RColor .yellow ),
142+ _signed_file_size ('-' , old_diff_size , RColor .red ),
143+ _signed_file_size ('+' , new_diff_size , RColor .green ),
56144 ])
57145 )
58146
59- def pretty_mode (mode : int ) -> RTextBase :
60- return TextComponents .file_mode (mode )
61-
62147 def reply_single (f : FileInfo , head : RTextBase ):
63148 text = RTextBase .format (
64- '{} {} {}' ,
149+ '{} {} {}{} ' ,
65150 head ,
66- pretty_mode (f .mode ),
151+ _pretty_mode (f .mode ),
152+ self .__make_uid_gid_columns (f ),
67153 TextComponents .file_path (f .path ),
68154 )
69155 if f .is_link ():
@@ -91,7 +177,7 @@ def make_hover(what_old: Optional[_T], what_new: Optional[_T], what_mapper: Call
91177
92178 if old_file .mode != new_file .mode :
93179 t_change = self .tr ('diff.mode' )
94- make_hover (pretty_mode (old_file .mode ), pretty_mode (new_file .mode ))
180+ make_hover (_pretty_mode (old_file .mode ), _pretty_mode (new_file .mode ))
95181 elif (sah1 := _map_or_none (old_file .blob , _get_raw_size_and_hash_from_blob )) != (sah2 := _map_or_none (new_file .blob , _get_raw_size_and_hash_from_blob )):
96182 t_change = self .tr ('diff.blob' )
97183 n = 8
@@ -109,7 +195,7 @@ def blob_what_mapper(sah: SizeAndHash):
109195 make_hover (old_file .content , new_file .content , lambda t : t .decode ('utf8' ))
110196 elif old_file .uid != new_file .uid or old_file .gid != new_file .gid :
111197 def format_owner (f : FileInfo ):
112- return RTextBase .format ('uid={} gid={}' , TextComponents . number (f .uid ), TextComponents . number (f .gid ))
198+ return RTextBase .format ('uid={} gid={}' , self . __uid_gid_resolver . uid (f .uid ), self . __uid_gid_resolver . gid (f .gid ))
113199 t_change = self .tr ('diff.owner' )
114200 make_hover (format_owner (old_file ), format_owner (new_file ))
115201 elif old_file .mtime != new_file .mtime :
@@ -125,10 +211,10 @@ def format_owner(f: FileInfo):
125211 t_change = RTextList (t_change , ' (' , RTextBase .join (', ' , hover_lines ), ')' )
126212
127213 self .reply (RTextBase .format (
128- '{} {} {}: {}' ,
214+ '{} {} {}{} : {}' ,
129215 RText ('[*]' , RColor .yellow ),
130- pretty_mode (new_file .mode ),
216+ _pretty_mode (new_file .mode ),
217+ self .__make_uid_gid_columns (new_file ),
131218 RText (old_file .path , TextColors .file ),
132219 t_change ,
133220 ))
134-
0 commit comments