DesktopFile.kt 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. package eu.mjdev.desktop.data
  2. import eu.mjdev.desktop.extensions.Custom.ParsedBoolean
  3. import eu.mjdev.desktop.extensions.Custom.ParsedList
  4. import eu.mjdev.desktop.extensions.Custom.ParsedString
  5. import eu.mjdev.desktop.extensions.Custom.text
  6. import eu.mjdev.desktop.helpers.system.LinuxIni
  7. import org.ini4j.Ini
  8. import org.ini4j.Profile.Section
  9. import org.ini4j.Wini
  10. import java.io.File
  11. @Suppress("unused", "MemberVisibilityCanBePrivate", "ConstPropertyName", "PropertyName")
  12. class DesktopFile(
  13. var file: File = File(""),
  14. val correctDir: String = "/tmp/.corrected-desktop-files/",
  15. val correctedFile: File = File(File(correctDir), file.name),
  16. var fileData: String = (if (correctedFile.exists()) correctedFile else file).text,
  17. private var content: Ini = (runCatching {
  18. LinuxIni(fileData)
  19. }.onFailure { e ->
  20. Exception("Error in file: ${file.absolutePath}", e).printStackTrace()
  21. }.getOrNull() ?: LinuxIni())
  22. ) {
  23. var fileName: String = if (correctedFile.exists()) correctedFile.name else file.name
  24. val absolutePath: String = if (correctedFile.exists()) correctedFile.absolutePath else file.absolutePath
  25. val fullAppName
  26. get() = if (correctedFile.exists()) correctedFile.nameWithoutExtension else file.nameWithoutExtension
  27. val sections: Map<String, Section>
  28. get() = content.map {
  29. if (it is Section) it else null
  30. }.filterNotNull().associateBy {
  31. it.value.toString()
  32. }
  33. val desktopSection : DesktopSectionScope?
  34. get() = content[DesktopEntryType.DesktopEntry]?.let { DesktopSectionScope(it) }
  35. val themeSection
  36. get() = content[DesktopEntryType.Theme]?.let { ThemeSectionScope(it) }
  37. val isApp
  38. get() = desktopSection?.Type == DesktopEntryType.Application
  39. val isCorrect
  40. get() = correctedFile.exists()
  41. init {
  42. if (isApp && !isCorrect) {
  43. runCatching {
  44. // println("Correcting file: file:///$absolutePath")
  45. // println("Corrected file: file:///${correctedFile.absolutePath}")
  46. correctedFile.let { f ->
  47. desktopSection?.StartupWMClass = fullAppName
  48. desktopSection?.OnlyShowIn = mutableListOf()
  49. writeTo(f)
  50. file = f
  51. fileData = file.text
  52. content = Wini(f)
  53. }
  54. }.onFailure { e ->
  55. e.printStackTrace()
  56. }
  57. }
  58. }
  59. fun section(
  60. type: DesktopEntryType,
  61. block: Section.() -> Unit
  62. ) = (content[type.text] ?: content.add(type.text))?.apply(block)
  63. fun desktopSection(
  64. block: DesktopSectionScope.() -> Unit
  65. ) = section(DesktopFile.DesktopEntryType.DesktopEntry) {
  66. DesktopSectionScope(this).apply(block)
  67. }
  68. fun themeSection(
  69. block: ThemeSectionScope.() -> Unit
  70. ) = section(DesktopFile.DesktopEntryType.Theme) {
  71. ThemeSectionScope(this).apply(block)
  72. }
  73. fun mkDirs(): DesktopFile {
  74. if (!file.parentFile.exists()) file.parentFile.mkdirs()
  75. return this
  76. }
  77. fun deleteFile(): DesktopFile {
  78. if (file.exists()) file.delete()
  79. return this
  80. }
  81. fun createNewFile(): DesktopFile {
  82. file.createNewFile()
  83. return this
  84. }
  85. fun newFile(): DesktopFile {
  86. mkDirs()
  87. deleteFile()
  88. createNewFile()
  89. return this
  90. }
  91. fun writeTo(f: File) {
  92. if (!f.parentFile.exists()) {
  93. f.parentFile.mkdirs()
  94. }
  95. if (f.exists()) {
  96. f.delete()
  97. }
  98. f.createNewFile()
  99. content.store(f)
  100. }
  101. fun write() =
  102. writeTo(file)
  103. interface ISectionScope {
  104. val section: Section
  105. }
  106. class DesktopSectionScope(
  107. override val section: Section
  108. ) : ISectionScope {
  109. var Type: DesktopEntryType
  110. get() = DesktopEntryType(section[Prop_Type])
  111. set(value) {
  112. section[Prop_Type] = value.text
  113. }
  114. var Version: String
  115. get() = ParsedString(section[Prop_Version])
  116. set(value) {
  117. section[Prop_Version] = value
  118. }
  119. var Name: String
  120. get() = ParsedString(section[Prop_Name])
  121. set(value) {
  122. section[Prop_Name] = value
  123. }
  124. var Comment: String
  125. get() = ParsedString(section[Prop_Comment])
  126. set(value) {
  127. section[Prop_Comment] = value
  128. }
  129. var Path: String
  130. get() = ParsedString(section[Prop_Path])
  131. set(value) {
  132. section[Prop_Path] = value
  133. }
  134. var Exec: String
  135. get() = ParsedString(section[Prop_Exec])
  136. set(value) {
  137. section[Prop_Exec] = value
  138. }
  139. var Icon: String
  140. get() = ParsedString(section[Prop_Icon])
  141. set(value) {
  142. section[Prop_Icon] = value
  143. }
  144. var Encoding: String
  145. get() = ParsedString(section[Prop_Encoding])
  146. set(value) {
  147. section[Prop_Encoding] = value
  148. }
  149. var NotifyOnStart: Boolean
  150. get() = ParsedBoolean(section[Prop_StartupNotify])
  151. set(value) {
  152. section[Prop_StartupNotify] = value.toString()
  153. }
  154. var RunInTerminal: Boolean
  155. get() = ParsedBoolean(section[Prop_Terminal])
  156. set(value) {
  157. section[Prop_Terminal] = value.toString()
  158. }
  159. var Categories: MutableList<String>
  160. get() = ParsedList(section[Prop_Categories])
  161. set(value) {
  162. section[Prop_Categories] = value.joinToString { "$it;" }
  163. }
  164. var OnlyShowIn: MutableList<String>
  165. get() = ParsedList(section[Prop_OnlyShowIn])
  166. set(value) {
  167. section[Prop_OnlyShowIn] = value.joinToString { "$it;" }
  168. }
  169. var StartupWMClass: String
  170. get() = ParsedString(section[Prop_StartupWMClass]).trim()
  171. set(value) {
  172. section[Prop_StartupWMClass] = value
  173. }
  174. }
  175. class ThemeSectionScope(
  176. override val section: Section
  177. ) : ISectionScope {
  178. var GtkTheme: String
  179. get() = ParsedString(section[Prop_GtkTheme])
  180. set(value) {
  181. section[Prop_GtkTheme] = value
  182. }
  183. var MetacityTheme: String
  184. get() = ParsedString(section[Prop_MetacityTheme])
  185. set(value) {
  186. section[Prop_MetacityTheme] = value
  187. }
  188. var IconTheme: String
  189. get() = ParsedString(section[Prop_IconTheme])
  190. set(value) {
  191. section[Prop_IconTheme] = value
  192. }
  193. var CursorTheme: String
  194. get() = ParsedString(section[Prop_CursorTheme])
  195. set(value) {
  196. section[Prop_CursorTheme] = value
  197. }
  198. var ButtonLayout: String
  199. get() = ParsedString(section[Prop_ButtonLayout])
  200. set(value) {
  201. section[Prop_ButtonLayout] = value
  202. }
  203. var UseOverlayScrollbars: Boolean
  204. get() = ParsedBoolean(section[Prop_X_Ubuntu_UseOverlayScrollbars])
  205. set(value) {
  206. section[Prop_X_Ubuntu_UseOverlayScrollbars] = value.toString()
  207. }
  208. }
  209. enum class DesktopEntryType(val text: String) {
  210. Unknown(""),
  211. DesktopEntry("Desktop Entry"),
  212. Application("Application"),
  213. Theme("X-GNOME-Metatheme");
  214. companion object {
  215. operator fun invoke(
  216. value: String?
  217. ): DesktopEntryType = value.orEmpty().trim().let { v ->
  218. entries.firstOrNull { e -> e.text.contentEquals(v, true) } ?: Unknown
  219. }
  220. }
  221. }
  222. companion object {
  223. const val EXTENSION: String = "desktop"
  224. const val Prop_Type = "Type"
  225. const val Prop_Version = "Version"
  226. const val Prop_Path = "Path"
  227. const val Prop_Name = "Name"
  228. const val Prop_Comment = "Comment"
  229. const val Prop_Exec = "Exec"
  230. const val Prop_StartupNotify = "StartupNotify"
  231. const val Prop_Terminal = "Terminal"
  232. const val Prop_Icon = "Icon"
  233. const val Prop_Categories = "Categories"
  234. const val Prop_StartupWMClass = "StartupWMClass"
  235. const val Prop_OnlyShowIn = "OnlyShowIn"
  236. // todo
  237. const val Prop_GenericName = "GenericName"
  238. const val Prop_MimeType = "MimeType"
  239. const val Prop_Actions = "Actions"
  240. const val Prop_DBusActivatable = "DBusActivatable"
  241. const val Prop_Encoding = "Encoding"
  242. const val Prop_Keywords = "Keywords"
  243. const val Prop_X_ExecArg = "X-ExecArg"
  244. const val Prop_X_Ubuntu_Gettext_Domain = "X-Ubuntu-Gettext-Domain"
  245. const val Prop_NoDisplay = "NoDisplay" // boolean
  246. const val Prop_X_GNOME_UsesNotifications = "X-GNOME-UsesNotifications"
  247. const val Prop_X_Purism_FormFactor = "X-Purism-FormFactor"
  248. const val Prop_X_Unity_IconBackgroundColor = "X-Unity-IconBackgroundColor"
  249. const val Prop_SingleMainWindow = "SingleMainWindow"
  250. // theme
  251. const val Prop_GtkTheme = "GtkTheme"
  252. const val Prop_MetacityTheme = "MetacityTheme"
  253. const val Prop_IconTheme = "IconTheme"
  254. const val Prop_CursorTheme = "CursorTheme"
  255. const val Prop_ButtonLayout = "ButtonLayout"
  256. const val Prop_X_Ubuntu_UseOverlayScrollbars = "X-Ubuntu-UseOverlayScrollbars"
  257. val Empty = DesktopFile()
  258. operator fun Ini.get(
  259. type: DesktopEntryType
  260. ) : Section? = get(type.text)
  261. fun desktopFile(
  262. file: File,
  263. block: DesktopFile.() -> Unit
  264. ) = DesktopFile(file).apply(block)
  265. }
  266. }