DesktopFile.kt 10 KB

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