CREATE TABLE `robot_task_actions`(
    `id` CHAR(36) NOT NULL,
    `action` VARCHAR(255) NOT NULL,
    `weight` INT NOT NULL default 0,
    `created` DATETIME NOT NULL,
    `modified` DATETIME NOT NULL,
    PRIMARY KEY(`id`)
);

CREATE TABLE `robot_tasks`(
    `id` CHAR(36) NOT NULL,
    `robot_task_action_id` CHAR(36) NOT NULL,
    `status` VARCHAR(255) NOT NULL default 'pending',
    `parameters` BLOB default NULL,
    `scheduled` DATETIME NOT NULL,
    `started` DATETIME default NULL,
    `finished` DATETIME default NULL,
    `created` DATETIME NOT NULL,
    `modified` DATETIME NOT NULL,
    PRIMARY KEY(`id`)
);

ALTER TABLE `robot_task_actions`
    ADD UNIQUE KEY `action`(`action`);

ALTER TABLE `robot_tasks`
    ADD KEY `robot_task_action_id`(`robot_task_action_id`),
    ADD CONSTRAINT `robot_tasks__robot_task_actions` FOREIGN KEY(`robot_task_action_id`) REFERENCES `robot_task_actions`(`id`);

CREATE TABLE `robot_task_actions`(
    `id` CHAR(36) NOT NULL,
    `action` VARCHAR(255) NOT NULL,
    `weight` INT NOT NULL default 0,
    `created` DATETIME NOT NULL,
    `modified` DATETIME NOT NULL,
    PRIMARY KEY(`id`)
);

CREATE TABLE `robot_tasks`(
    `id` CHAR(36) NOT NULL,
    `robot_task_action_id` CHAR(36) NOT NULL,
    `action` VARCHAR(255) NOT NULL,
    `weight` INT NOT NULL,
    `status` VARCHAR(255) NOT NULL default 'pending',
    `parameters` BLOB default NULL,
    `scheduled` DATETIME NOT NULL,
    `started` DATETIME default NULL,
    `finished` DATETIME default NULL,
    `created` DATETIME NOT NULL,
    `modified` DATETIME NOT NULL,
    PRIMARY KEY(`id`)
);

ALTER TABLE `robot_task_actions`
    ADD UNIQUE KEY `action`(`action`);

ALTER TABLE `robot_tasks`
    ADD KEY `robot_task_action_id`(`robot_task_action_id`),
    ADD CONSTRAINT `robot_tasks__robot_task_actions` FOREIGN KEY(`robot_task_action_id`) REFERENCES `robot_task_actions`(`id`),
	ADD INDEX `status__weight__scheduled` (`status`, `weight`, `scheduled`);


# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
import unittest

from messaging.sms import SmsSubmit, SmsDeliver
from messaging.utils import (timedelta_to_relative_validity as to_relative,
                             datetime_to_absolute_validity as to_absolute,
                             FixedOffset)

class TestEncodingFunctions(unittest.TestCase):

    def test_converting_timedelta_to_validity(self):
        self.assertEqual(to_relative(timedelta(minutes=5)), 0)
        self.assertEqual(to_relative(timedelta(minutes=6)), 0)
        self.assertEqual(to_relative(timedelta(minutes=10)), 1)

        self.assertEqual(to_relative(timedelta(hours=12)), 143)
        self.assertEqual(to_relative(timedelta(hours=13)), 145)
        self.assertEqual(to_relative(timedelta(hours=24)), 167)

        self.assertEqual(to_relative(timedelta(days=2)), 168)
        self.assertEqual(to_relative(timedelta(days=30)), 196)

    def test_converting_datetime_to_validity(self):
        # http://www.dreamfabric.com/sms/scts.html
        # 12. Feb 1999 05:57:30 GMT+3
        when = datetime(1999, 2, 12, 5, 57, 30, 0,
                         FixedOffset(3 * 60, "GMT+3"))
        expected = [0x99, 0x20, 0x21, 0x50, 0x75, 0x03, 0x21]
        self.assertEqual(to_absolute(when, "GMT+3"), expected)

        when = datetime(1999, 2, 12, 5, 57, 30, 0)
        expected = [0x99, 0x20, 0x21, 0x50, 0x75, 0x03, 0x0]
        self.assertEqual(to_absolute(when, "UTC"), expected)

        when = datetime(1999, 2, 12, 5, 57, 30, 0,
                         FixedOffset(-3 * 60, "GMT-3"))
        expected = [0x99, 0x20, 0x21, 0x50, 0x75, 0x03, 0x29]
        self.assertEqual(to_absolute(when, "GMT-3"), expected)

class TestSmsSubmit(unittest.TestCase):

    def test_encoding_validity(self):
        # no validity
        number = "+34616585119"
        text = "hola"
        expected = "0001000B914316565811F9000004E8373B0C"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

        # absolute validity
        number = "+34616585119"
        text = "hola"
        expected = "0019000B914316565811F900000170520251930004E8373B0C"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.validity = datetime(2010, 7, 25, 20, 15, 39)

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

        # relative validity
        number = "+34616585119"
        text = "hola"
        expected = "0011000B914316565811F90000AA04E8373B0C"
        expected_len = 18

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.validity = timedelta(days=4)

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)
        self.assertEqual(pdu.length, expected_len)

    def test_encoding_csca(self):
        number = "+34616585119"
        text = "hola"
        csca = "+34646456456"
        expected = "07914346466554F601000B914316565811F9000004E8373B0C"
        expected_len = 17

        sms = SmsSubmit(number, text)
        sms.csca = csca
        sms.ref = 0x0

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)
        self.assertEqual(pdu.length, expected_len)
        self.assertEqual(pdu.cnt, 1)
        self.assertEqual(pdu.seq, 1)

    def test_encoding_class(self):
        number = "+34654123456"
        text = "hey yo"
        expected_0 = "0001000B914356143254F6001006E8721E947F03"
        expected_1 = "0001000B914356143254F6001106E8721E947F03"
        expected_2 = "0001000B914356143254F6001206E8721E947F03"
        expected_3 = "0001000B914356143254F6001306E8721E947F03"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.klass = 0

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected_0)

        sms.klass = 1
        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected_1)

        sms.klass = 2
        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected_2)

        sms.klass = 3
        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected_3)

    def test_encoding_request_status(self):
        # tested with pduspy.exe and http://www.rednaxela.net/pdu.php
        number = "+34654123456"
        text = "hey yo"
        expected = "0021000B914356143254F6000006E8721E947F03"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.request_status = True

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

    def test_encoding_message_with_latin1_chars(self):
        # tested with pduspy.exe
        number = "+34654123456"
        text = u"Hölä"
        expected = "0011000B914356143254F60000AA04483E7B0F"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.validity = timedelta(days=4)

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

        # tested with pduspy.exe
        number = "+34654123456"
        text = u"BÄRÇA äñ@"
        expected = "0001000B914356143254F6000009C2AD341104EDFB00"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

    def test_encoding_8bit_message(self):
        number = "01000000000"
        csca = "+44000000000"
        text = "Hi there..."
        expected = "07914400000000F001000B811000000000F000040B48692074686572652E2E2E"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.csca = csca
        sms.fmt = 0x04  # 8 bits

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

    def test_encoding_ucs2_message(self):
        number = "+34616585119"
        text = u'あ叶葉'
        csca = '+34646456456'
        expected = "07914346466554F601000B914316565811F9000806304253F68449"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.csca = csca

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

        text = u"Русский"
        number = "655345678"
        expected = "001100098156355476F80008AA0E0420044304410441043A04380439"

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.validity = timedelta(days=4)

        pdu = sms.to_pdu()[0]
        self.assertEqual(pdu.pdu, expected)

    def test_encoding_multipart_7bit(self):
        # text encoded with umts-tools
        text = "Or walk with Kings - nor lose the common touch, if neither foes nor loving friends can hurt you, If all men count with you, but none too much; If you can fill the unforgiving minute With sixty seconds' worth of distance run, Yours is the Earth and everything thats in it, And - which is more - you will be a Man, my son"
        number = "655345678"
        expected = [
            "005100098156355476F80000AAA00500038803019E72D03DCC5E83EE693A1AB44CBBCF73500BE47ECB41ECF7BC0CA2A3CBA0F1BBDD7EBB41F4777D8C6681D26690BB9CA6A3CB7290F95D9E83DC6F3988FDB6A7DD6790599E2EBBC973D038EC06A1EB723A28FFAEB340493328CC6683DA653768FCAEBBE9A07B9A8E06E5DF7516485CA783DC6F7719447FBF41EDFA18BD0325CDA0FCBB0E1A87DD",
            "005100098156355476F80000AAA005000388030240E6349B0DA2A3CBA0BADBFC969FD3F6B4FB0C6AA7DD757A19744DD3D1A0791A4FCF83E6E5F1DB4D9E9F40F7B79C8E06BDCD20727A4E0FBBC76590BCEE6681B2EFBA7C0E4ACF41747419540CCBE96850D84D0695ED65799E8E4EBBCF203A3A4C9F83D26E509ACE0205DD64500B7447A7C768507A0E6ABFE565500B947FD741F7349B0D129741",
            "005100098156355476F80000AA14050003880303C2A066D8CD02B5F3A0F9DB0D",
        ]

        sms = SmsSubmit(number, text)
        sms.ref = 0x0
        sms.rand_id = 136
        sms.validity = timedelta(days=4)

        ret = sms.to_pdu()
        cnt = len(ret)
        for i, pdu in enumerate(ret):
            self.assertEqual(pdu.pdu, expected[i])
            self.assertEqual(pdu.seq, i + 1)
            self.assertEqual(pdu.cnt, cnt)

    def test_encoding_bad_number_raises_error(self):
        self.assertRaises(ValueError, SmsSubmit, "032BADNUMBER", "text")

    def test_encoding_bad_csca_raises_error(self):
        sms = SmsSubmit("54342342", "text")
        self.assertRaises(ValueError, setattr, sms, 'csca', "1badcsca")

class TestSubmitPduCounts(unittest.TestCase):

    DEST = "+3530000000"
    GSM_CHAR = "x"
    EGSM_CHAR = u"€"
    UNICODE_CHAR = u"ő"

    def test_gsm_1(self):
        sms = SmsSubmit(self.DEST, self.GSM_CHAR * 160)
        self.assertEqual(len(sms.to_pdu()), 1)

    def test_gsm_2(self):
        sms = SmsSubmit(self.DEST, self.GSM_CHAR * 161)
        self.assertEqual(len(sms.to_pdu()), 2)

    def test_gsm_3(self):
        sms = SmsSubmit(self.DEST, self.GSM_CHAR * 153 * 2)
        self.assertEqual(len(sms.to_pdu()), 2)

    def test_gsm_4(self):
        sms = SmsSubmit(self.DEST,
                        self.GSM_CHAR * 153 * 2 + self.GSM_CHAR)
        self.assertEqual(len(sms.to_pdu()), 3)

    def test_gsm_5(self):
        sms = SmsSubmit(self.DEST, self.GSM_CHAR * 153 * 3)
        self.assertEqual(len(sms.to_pdu()), 3)

    def test_gsm_6(self):
        sms = SmsSubmit(self.DEST,
                        self.GSM_CHAR * 153 * 3 + self.GSM_CHAR)
        self.assertEqual(len(sms.to_pdu()), 4)

    def test_egsm_1(self):
        sms = SmsSubmit(self.DEST, self.EGSM_CHAR * 80)
        self.assertEqual(len(sms.to_pdu()), 1)

    def test_egsm_2(self):
        sms = SmsSubmit(self.DEST,
                        self.EGSM_CHAR * 79 + self.GSM_CHAR)
        self.assertEqual(len(sms.to_pdu()), 1)

    def test_egsm_3(self):
        sms = SmsSubmit(self.DEST, self.EGSM_CHAR * 153)  # 306 septets
        self.assertEqual(len(sms.to_pdu()), 3)

    def test_egsm_4(self):
        sms = SmsSubmit(self.DEST,
                          self.EGSM_CHAR * 229 + self.GSM_CHAR)  # 459 septets
        self.assertEqual(len(sms.to_pdu()), 4)

    def test_unicode_1(self):
        sms = SmsSubmit(self.DEST, self.UNICODE_CHAR * 70)
        self.assertEqual(len(sms.to_pdu()), 1)

    def test_unicode_2(self):
        sms = SmsSubmit(self.DEST, self.UNICODE_CHAR * 70 + self.GSM_CHAR)
        self.assertEqual(len(sms.to_pdu()), 2)

    def test_unicode_3(self):
        sms = SmsSubmit(self.DEST, self.UNICODE_CHAR * 67 * 2)
        self.assertEqual(len(sms.to_pdu()), 2)

    def test_unicode_4(self):
        sms = SmsSubmit(self.DEST, self.UNICODE_CHAR * 67 * 2 + self.GSM_CHAR)
        self.assertEqual(len(sms.to_pdu()), 3)

    def test_unicode_5(self):
        sms = SmsSubmit(self.DEST, self.UNICODE_CHAR * 67 * 3)
        self.assertEqual(len(sms.to_pdu()), 3)

    def test_unicode_6(self):
        sms = SmsSubmit(self.DEST, self.UNICODE_CHAR * 67 * 3 + self.GSM_CHAR)
        self.assertEqual(len(sms.to_pdu()), 4)

class TestSmsDeliver(unittest.TestCase):

    def test_decoding_7bit_pdu(self):
        pdu = "07911326040000F0040B911346610089F60000208062917314080CC8F71D14969741F977FD07"
        text = "How are you?"
        csca = "+31624000000"
        number = "+31641600986"

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.text, text)
        self.assertEqual(sms.csca, csca)
        self.assertEqual(sms.number, number)

    def test_decoding_ucs2_pdu(self):
        pdu = "07914306073011F0040B914316709807F2000880604290224080084E2D5174901A8BAF"
        text = u"中兴通讯"
        csca = "+34607003110"
        number = "+34610789702"

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.text, text)
        self.assertEqual(sms.csca, csca)
        self.assertEqual(sms.number, number)

    def test_decoding_7bit_pdu_data(self):
        pdu = "07911326040000F0040B911346610089F60000208062917314080CC8F71D14969741F977FD07"
        text = "How are you?"
        csca = "+31624000000"
        number = "+31641600986"

        data = SmsDeliver(pdu).data
        self.assertEqual(data['text'], text)
        self.assertEqual(data['csca'], csca)
        self.assertEqual(data['number'], number)
        self.assertEqual(data['pid'], 0)
        self.assertEqual(data['fmt'], 0)
        self.assertEqual(data['date'], datetime(2002, 8, 26, 19, 37, 41))

    def test_decoding_datetime_gmtplusone(self):
        pdu = "0791447758100650040C914497716247010000909010711423400A2050EC468B81C4733A"
        text = "  1741 bst"
        number = "+447917267410"
        date = datetime(2009, 9, 1, 16, 41, 32)

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.text, text)
        self.assertEqual(sms.number, number)
        self.assertEqual(sms.date, date)

    def test_decoding_datetime_gmtminusthree(self):
        pdu = "0791553001000001040491578800000190115101112979CF340B342F9FEBE536E83D0791C3E4F71C440E83E6F53068FE66A7C7697A781C7EBB4050F99BFE1EBFD96F1D48068BC16030182E66ABD560B41988FC06D1D3F03768FA66A7C7697A781C7E83CCEF34282C2ECBE96F50B90D8AC55EB0DC4B068BC140B1994E16D3D1622E"
        date = datetime(2010, 9, 11, 18, 10, 11)  # 11/09/10 15:10 GMT-3.00

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.date, date)

    def test_decoding_number_alphanumeric(self):
        # Odd length test
        pdu = "07919471060040340409D0C6A733390400009060920173018093CC74595C96838C4F6772085AD6DDE4320B444E9741D4B03C6D7EC3E9E9B71B9474D3CB727799DEA286CFE5B9991DA6CBC3F432E85E9793CBA0F09A9EB6A7CB72BA0B9474D3CB727799DE72D6E9FABAFB0CBAA7E56490BA4CD7D34170F91BE4ACD3F575F7794E0F9F4161F1B92C2F8FD1EE32DD054AA2E520E3D3991C82A8E5701B"
        number = "FONIC"
        text = "Lieber FONIC Kunde, die Tarifoption Internet-Tagesflatrate wurde aktiviert. Internet-Nutzung wird jetzt pro Nutzungstag abgerechnet. Ihr FONIC Team"
        csca = "+491760000443"

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.text, text)
        self.assertEqual(sms.csca, csca)
        self.assertEqual(sms.number, number)

        # Even length test
        pdu = "07919333852804000412D0F7FBDD454FB75D693A0000903002801153402BCD301E9F0605D9E971191483C140412A35690D52832063D2F9040599A058EE05A3BD6430580E"
        number = "www.tim.it"
        text = 'Maxxi Alice 100 ATTIVATA FINO AL 19/04/2009'
        csca = '+393358824000'

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.text, text)
        self.assertEqual(sms.csca, csca)
        self.assertEqual(sms.number, number)

    def test_decode_sms_confirmation(self):
        pdu = "07914306073011F006270B913426565711F7012081111345400120811174054043"
        csca = "+34607003110"
        date = datetime(2010, 2, 18, 11, 31, 54)
        number = "SR-UNKNOWN"
        # XXX: the number should be +344626575117, is the prefix flipped ?
        text = "+43626575117|10/02/18 11:31:54|"

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.text, text)
        self.assertEqual(sms.csca, csca)
        self.assertEqual(sms.number, number)
        self.assertEqual(sms.date, date)

        # weird sms confirmation
        pdu = "07914306073011F001000B914306565711F9000007F0B2FC0DCABF01"
        csca = "+34607003110"
        number = "SR-UNKNOWN"

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.csca, csca)
        self.assertEqual(sms.number, number)

    def test_decode_weird_multipart_german_pdu(self):
        pdus = [
            "07919471227210244405852122F039F101506271217180A005000319020198E9B2B82C0759DFE4B0F9ED2EB7967537B9CC02B5D37450122D2FCB41EE303DFD7687D96537881A96A7CD6F383DFD7683F46134BBEC064DD36550DA0D22A7CBF3721BE42CD3F5A0198B56036DCA20B8FC0D6A0A4170767D0EAAE540433A082E7F83A6E5F93CFD76BB40D7B2DB0D9AA6CB2072BA3C2F83926EF31BE44E8FD17450BB8C9683CA",
            "07919471227210244405852122F039F1015062712181804F050003190202E4E8309B5E7683DAFC319A5E76B340F73D9A5D7683A6E93268FD9ED3CB6EF67B0E5AD172B19B2C2693C9602E90355D6683A6F0B007946E8382F5393BEC26BB00",
        ]
        texts = [
            u"Lieber Vodafone-Kunde, mit Ihrer nationalen Tarifoption zahlen Sie in diesem Netz 3,45 € pro MB plus 59 Ct pro Session. Wenn Sie diese Info nicht mehr e",
            u"rhalten möchten, wählen Sie kostenlos +4917212220. Viel Spaß im Ausland.",
        ]

        for i, sms in enumerate(map(SmsDeliver, pdus)):
            self.assertEqual(sms.text, texts[i])
            self.assertEqual(sms.udh.concat.cnt, len(pdus))
            self.assertEqual(sms.udh.concat.seq, i + 1)
            self.assertEqual(sms.udh.concat.ref, 25)

    def test_decoding_odd_length_pdu_strict_raises_valueerror(self):
        # same pdu as in test_decoding_number_alpha1 minus last char
        pdu = "07919471060040340409D0C6A733390400009060920173018093CC74595C96838C4F6772085AD6DDE4320B444E9741D4B03C6D7EC3E9E9B71B9474D3CB727799DEA286CFE5B9991DA6CBC3F432E85E9793CBA0F09A9EB6A7CB72BA0B9474D3CB727799DE72D6E9FABAFB0CBAA7E56490BA4CD7D34170F91BE4ACD3F575F7794E0F9F4161F1B92C2F8FD1EE32DD054AA2E520E3D3991C82A8E5701"
        self.assertRaises(ValueError, SmsDeliver, pdu)

    def test_decoding_odd_length_pdu_no_strict(self):
        # same pdu as in test_decoding_number_alpha1 minus last char
        pdu = "07919471060040340409D0C6A733390400009060920173018093CC74595C96838C4F6772085AD6DDE4320B444E9741D4B03C6D7EC3E9E9B71B9474D3CB727799DEA286CFE5B9991DA6CBC3F432E85E9793CBA0F09A9EB6A7CB72BA0B9474D3CB727799DE72D6E9FABAFB0CBAA7E56490BA4CD7D34170F91BE4ACD3F575F7794E0F9F4161F1B92C2F8FD1EE32DD054AA2E520E3D3991C82A8E5701"
        text = "Lieber FONIC Kunde, die Tarifoption Internet-Tagesflatrate wurde aktiviert. Internet-Nutzung wird jetzt pro Nutzungstag abgerechnet. Ihr FONIC Tea"

        sms = SmsDeliver(pdu, strict=False)
        self.assertEqual(sms.text, text)

    def test_decoding_delivery_status_report(self):
        pdu = "0791538375000075061805810531F1019082416500400190824165004000"
        sr = {
            'status': 0,
            'scts': datetime(2010, 9, 28, 14, 56),
            'dt': datetime(2010, 9, 28, 14, 56),
            'recipient': '50131'
        }

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.csca, "+353857000057")
        data = sms.data
        self.assertEqual(data['ref'], 24)
        self.assertEqual(sms.sr, sr)

    def test_decoding_delivery_status_report_without_smsc_address(self):
        pdu = "00060505810531F1010150610000400101506100004000"
        sr = {
            'status': 0,
            'scts': datetime(2010, 10, 5, 16, 0),
            'dt': datetime(2010, 10, 5, 16, 0),
            'recipient': '50131'
        }

        sms = SmsDeliver(pdu)
        self.assertEqual(sms.csca, None)
        data = sms.data
        self.assertEqual(data['ref'], 5)
        self.assertEqual(sms.sr, sr)


module GitHosting
	module Patches
		module GitAdapterPatch

			def self.included(base)
				base.class_eval do
					unloadable
				end

				begin
					base.send(:alias_method_chain, :scm_cmd, :sudo)
				rescue Exception =>e
				end

				base.extend(ClassMethods)
				base.class_eval do
					class << self
						alias_method_chain :sq_bin, :sudo
						begin
							alias_method_chain :client_command, :sudo
						rescue Exception =>e
						end
					end
				end
			end

			module ClassMethods
				def sq_bin_with_sudo
					return Redmine::Scm::Adapters::GitAdapter::shell_quote(GitHosting::git_exec())
				end
				def client_command_with_sudo
					return GitHosting::git_exec()
				end
			end

			def scm_cmd_with_sudo(*args, &block)

				args.each do |a|
					a.gsub!(/^\.\-\w_\:]/, '')
				end
				max_cache_time     = (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i             # in seconds, default = 60
				max_cache_elements = (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i         # default = 100
				max_cache_size     = (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024   # In MB, default = 16MB, converted to bytes

				repo_path = root_url || url
				full_args = [GitHosting::git_exec(), '--git-dir', repo_path]
				if self.class.client_version_above?([1, 7, 2])
					full_args << '-c' << 'core.quotepath=false'
					full_args << '-c' << 'log.decorate=no'
				end
				full_args += args

				cmd_str=full_args.map { |e| shell_quote e.to_s }.join(' ')
				out=nil
				retio = nil
				cached=GitCache.find_by_command(cmd_str)
				if cached != nil
					cur_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
					if cur_time.to_i - cached.created_at.to_i < max_cache_time || max_cache_time < 0
						out = cached.command_output == nil ? "" : cached.command_output
						#File.open("/tmp/command_output.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") }
					else
						GitCache.destroy(cached.id)
					end
				end
				if out == nil
					shellout(cmd_str) do |io|
						out = io.read(max_cache_size + 1)
					end
					out = out == nil ? "" : out

					if $? && $?.exitstatus != 0
						raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
					elsif out.length <= max_cache_size
						proj_id=repo_path.gsub(/\.git$/, "").gsub(/^.*\//, "")
						gitc = GitCache.create( :command=>cmd_str, :command_output=>out, :proj_identifier=>proj_id )
						gitc.save
						if GitCache.count > max_cache_elements && max_cache_elements >= 0
							oldest = GitCache.find(:last, :order => "created_at DESC")
							GitCache.destroy(oldest.id)
						end
						#File.open("/tmp/non_cached.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") }
					else
						retio = shellout(cmd_str, &block)
						if $? && $?.exitstatus != 0
							raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
						end

					end
				end

				if retio == nil
					retio = StringIO.new(string=out)
					if block_given?
						block.call(retio)
					end
				end
				retio
			end

		end
	end
end

module GitHosting
	module Patches
		module GitAdapterPatch

			def self.included(base)
				base.class_eval do
					unloadable
				end

				begin
					base.send(:alias_method_chain, :scm_cmd, :sudo)
				rescue Exception =>e
				end

				base.extend(ClassMethods)
				base.class_eval do
					class << self
						alias_method_chain :sq_bin, :sudo
						begin
							alias_method_chain :client_command, :sudo
						rescue Exception =>e
						end
					end
				end
			end

			module ClassMethods
				def sq_bin_with_sudo
					return Redmine::Scm::Adapters::GitAdapter::shell_quote(GitHosting::git_exec())
				end
				def client_command_with_sudo
					return GitHosting::git_exec()
				end
			end

			def scm_cmd_with_sudo(*args, &block)

				args.each do |a|
					a.gsub!(/^\.\-\w_\:\]/, '')
				end
				max_cache_time     = (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i             # in seconds, default = 60
				max_cache_elements = (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i         # default = 100
				max_cache_size     = (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024   # In MB, default = 16MB, converted to bytes

				repo_path = root_url || url
				full_args = [GitHosting::git_exec(), '--git-dir', repo_path]
				if self.class.client_version_above?([1, 7, 2])
					full_args << '-c' << 'core.quotepath=false'
					full_args << '-c' << 'log.decorate=no'
				end
				full_args += args

				cmd_str=full_args.map { |e| shell_quote e.to_s }.join(' ')
				out=nil
				retio = nil
				cached=GitCache.find_by_command(cmd_str)
				if cached != nil
					cur_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
					if cur_time.to_i - cached.created_at.to_i < max_cache_time || max_cache_time < 0
						out = cached.command_output == nil ? "" : cached.command_output
						#File.open("/tmp/command_output.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") }
					else
						GitCache.destroy(cached.id)
					end
				end
				if out == nil
					shellout(cmd_str) do |io|
						out = io.read(max_cache_size + 1)
					end
					out = out == nil ? "" : out

					if $? && $?.exitstatus != 0
						raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
					elsif out.length <= max_cache_size
						proj_id=repo_path.gsub(/\.git$/, "").gsub(/^.*\//, "")
						gitc = GitCache.create( :command=>cmd_str, :command_output=>out, :proj_identifier=>proj_id )
						gitc.save
						if GitCache.count > max_cache_elements && max_cache_elements >= 0
							oldest = GitCache.find(:last, :order => "created_at DESC")
							GitCache.destroy(oldest.id)
						end
						#File.open("/tmp/non_cached.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") }
					else
						retio = shellout(cmd_str, &block)
						if $? && $?.exitstatus != 0
							raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
						end

					end
				end

				if retio == nil
					retio = StringIO.new(string=out)
					if block_given?
						block.call(retio)
					end
				end
				retio
			end

		end
	end
end


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#include <moar.h>

#ifdef _WIN32
#  include <sys/types.h>
#  include <sys/stat.h>
#  include <process.h>
#  include <shlwapi.h>
#  include <io.h>
#  if defined(_MSC_VER)
#    define strtoll _strtoi64
#  endif
#else
#  include <sys/stat.h>
#  include <libgen.h>
#  include <unistd.h>
#  include "signal.h"
#endif

#define STRINGIFY1(x) #x
#define STRINGIFY(x) STRINGIFY1(x)

/* flags need to be sorted alphabetically */

enum {
    NOT_A_FLAG = -2,
    UNKNOWN_FLAG = -1,

    FLAG_SUSPEND,
    FLAG_FULL_CLEANUP,
    FLAG_TRACING,

    OPT_DEBUGPORT,

    OPT_RAKUDO_HOME
};

static const char *const FLAGS[] = {
    "--debug-suspend",
    "--full-cleanup",
    "--tracing",
};

static int cmp_flag(const void *key, const void *value) {
    return strcmp(key, *(char **)value);
}

static int starts_with(const char *str, const char *want) {
    size_t str_len  = strlen(str);
    size_t want_len = strlen(want);
    return str_len < want_len
        ? 0
        : strncmp(str, want, want_len) == 0;
}

static int parse_flag(const char *arg)
{
    const char *const *found;

    if (!arg || arg[0] != '-')
        return NOT_A_FLAG;

    found = bsearch(arg, FLAGS, sizeof FLAGS / sizeof *FLAGS, sizeof *FLAGS, cmp_flag);

    if (found)
        return (int)(found - FLAGS);
    else if (starts_with(arg, "--debug-port="))
        return OPT_DEBUGPORT;
    else if (starts_with(arg, "--rakudo-home="))
        return OPT_RAKUDO_HOME;
    else
        return UNKNOWN_FLAG;
}

int file_exists(const char *path) {
#ifdef _WIN32
    int             res;
    struct _stat    sb;
    const int       len   = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
    wchar_t * const wpath = (wchar_t *)malloc(len * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, path, -1, (LPWSTR)wpath, len);
    res = _wstat(wpath, &sb);
    free(wpath);
    return res == 0;
#else
    struct stat *sb = malloc(sizeof(struct stat));
    int res         = stat(path, sb) == 0;
    free(sb);
    return res;
#endif
}

void platformify_path(char *path) {
#ifdef _WIN32
    int i;
    for (i = 0; path[i]; i++) {
        if (path[i] == '/') {
            path[i] = '\\';
        }
    }
#endif
}

int retrieve_home(
          char  **out_home,
    const char   *rel_home,
    const size_t  rel_home_size,
    const char   *env_var,
          char   *exec_dir_path,
          size_t  exec_dir_path_size,
    const char   *check_file,
    const size_t  check_file_size,
          char   *static_home,
          char   *options_home
) {
    char   *check_file_path;
    size_t  home_size;
    int     ret;
    char   *env_home         = getenv(env_var);

    if (options_home) {
        *out_home = options_home;
        home_size = strlen(*out_home);
    }
    else if (env_home) {
        home_size = strlen(env_home);
        *out_home = (char*)malloc(home_size + 1);
        strcpy(*out_home, env_home);
#ifdef _WIN32
        if (*(*out_home + home_size - 1) == '\\') {
#else
        if (*(*out_home + home_size - 1) == '/') {
#endif
            *(*out_home + home_size - 1) = '\0';
            home_size--;
        }
    }
    else if (static_home) {
        *out_home = static_home;
        home_size = strlen(*out_home);
    }
    else {
        home_size = exec_dir_path_size + rel_home_size;
        *out_home = (char*)malloc(home_size + 1);
        strncpy(*out_home, exec_dir_path, home_size);
        strncat(*out_home, rel_home, rel_home_size);
        platformify_path(*out_home + exec_dir_path_size);
    }

    check_file_path = (char*)malloc(home_size + check_file_size + 1);
    strncpy(check_file_path, *out_home, home_size + check_file_size);
    strncat(check_file_path, check_file, check_file_size);

    ret = file_exists(check_file_path);
    free(check_file_path);
    return ret;
}

#if defined(_WIN32) && defined(SUBSYSTEM_WINDOWS)
int set_std_handle_to_nul(FILE *file, int fd, BOOL read, int std_handle_type) {
    /* Found on https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2019:

       "When stdin, stdout, and stderr aren't associated with a stream (for example, in a Windows
       application without a console window), the file descriptor values for these streams are
       returned from _fileno as the special value -2. Similarly, if you use a 0, 1, or 2 as the
       file descriptor parameter instead of the result of a call to _fileno, _get_osfhandle also
       returns the special value -2 when the file descriptor is not associated with a stream, and
       does not set errno. However, this is not a valid file handle value, and subsequent calls
       that attempt to use it are likely to fail."

       See https://jdebp.eu/FGA/redirecting-standard-io.html
           https://stackoverflow.com/a/50358201 (Especially the comments of Eryk Sun)
    */
    FILE *stream;
    HANDLE new_handle;

    if (_fileno(file) != -2 || _get_osfhandle(fd) != -2)
        // The handles are initialized. Don't touch!
        return 1;

    /* FD 1 is in an error state (_get_osfhandle(1) == -2). Close it. The FD number is up for grabs
       after this call. */
    if (_close(fd) != 0)
        return 0;

    /* FILE *stdout is in an error state (_fileno(stdout) == -2). Reopen it to a "NUL:" file. This
       will take the next free FD number. So it's important to call this sequentially for FD 0, 1
       and 2. */
    if (freopen_s(&stream, "NUL:", read ? "r" : "w", file) != 0)
        return 0;

    /* Set the underlying Windows handle as the STD handler. */
    new_handle = (HANDLE)_get_osfhandle(fd);
    if (!SetStdHandle(std_handle_type, new_handle))
        return 0;

    return 1;
}
#endif

#if defined(_WIN32) && defined(SUBSYSTEM_WINDOWS)
int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, INT nCmdShow) {
    int argc;
    LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
    char **argv = MVM_UnicodeToUTF8_argv(argc, wargv);
    LocalFree(wargv);
#elif defined(_WIN32) && !defined(SUBSYSTEM_WINDOWS)
int wmain(int argc, wchar_t *wargv[]) {
    char **argv = MVM_UnicodeToUTF8_argv(argc, wargv);
#else
int main(int argc, char *argv[]) {
#endif
    MVMInstance *instance;

    char   *exec_path;
    size_t  exec_path_size;

    char   *exec_dir_path_temp;
    char   *exec_dir_path;
    size_t  exec_dir_path_size;

          char   *nqp_home;
          size_t  nqp_home_size;
          char   *static_nqp_home     = 0;
    const char    nqp_rel_path[14]    = "/../share/nqp";
    const size_t  nqp_rel_path_size   = 13;
    const char    nqp_check_path[28]  = "/lib/NQPCORE.setting.moarvm";
    const size_t  nqp_check_path_size = 27;

          char   *rakudo_home;
          size_t  rakudo_home_size;
          char   *static_rakudo_home    = 0;
          char   *option_rakudo_home    = 0;
    const char    perl6_rel_path[16]    = "/../share/perl6";
    const size_t  perl6_rel_path_size   = 15;
    const char    perl6_check_path[22]  = "/runtime/perl6.moarvm";
    const size_t  perl6_check_path_size = 21;

    char *lib_path[3];
    char *perl6_file;

    int full_cleanup = 0;
    int argi         = 1;
    int flag;
    int new_argc     = 0;

    MVMuint32 debugserverport = 0;
    int start_suspended = 0;

#if defined(__APPLE__) || defined(__Darwin__)
    if (getenv("RAKUDO_DYLD_LIBRARY_PATH")) {
        setenv("DYLD_LIBRARY_PATH", getenv("RAKUDO_DYLD_LIBRARY_PATH"), 0);
    }
#endif

#if defined(_WIN32) && defined(SUBSYSTEM_WINDOWS)
    /* When using the 'windows' subsystem the standard IO handles are not
       connected. This causes a program abort when accessing the handles. To
       prevent these aborts, we redirect the handles to NUL in this case.
    */

    /* Set our own handles. */
    if (!set_std_handle_to_nul(stdin,  0, 1, STD_INPUT_HANDLE))  return EXIT_FAILURE;
    if (!set_std_handle_to_nul(stdout, 1, 0, STD_OUTPUT_HANDLE)) return EXIT_FAILURE;
    if (!set_std_handle_to_nul(stderr, 2, 0, STD_ERROR_HANDLE))  return EXIT_FAILURE;

    /* MoarVM - as a DLL, and the way it's compiled (/MT) has it's own CRT and thus it's own CRT STD handles.
       So MoarVM also needs to fix up its CRT STD handles.
       See: https://docs.microsoft.com/de-de/cpp/c-runtime-library/potential-errors-passing-crt-objects-across-dll-boundaries
            https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features
    */
    if (!MVM_set_std_handles_to_nul()) return EXIT_FAILURE;
#endif

    /* Retrieve the executable directory path. */

#ifdef STATIC_EXEC_PATH
    exec_path = STRINGIFY(STATIC_EXEC_PATH);
    exec_path_size = strlen(exec_path);
#else
    int res;
    exec_path_size = 4096;
    exec_path = (char*)malloc(exec_path_size);
    res = MVM_exepath(exec_path, &exec_path_size);
    while (res < 0 && exec_path_size < 4096*8) {
        exec_path_size *= 2;
        exec_path = (char*)realloc(exec_path, exec_path_size);
        res = MVM_exepath(exec_path, &exec_path_size);
    }
    if (res < 0) {
        fprintf(stderr, "ERROR: Could not retrieve executable path.\n");
        return EXIT_FAILURE;
    }
#endif

    /* Filter out VM arguments from the command line. */

    for (; (flag = parse_flag(argv[argi])) != NOT_A_FLAG; ++argi) {
        switch (flag) {
            case FLAG_FULL_CLEANUP:
            full_cleanup = 1;
            continue;

#if MVM_TRACING
            case FLAG_TRACING:
            MVM_interp_enable_tracing();
            continue;
#endif

            case FLAG_SUSPEND:
            start_suspended = 1;
            continue;

            case OPT_DEBUGPORT: {
                MVMint64 port;
                char *portstr = argv[argi] + strlen("--debug-port=");
                char *endptr;
                port = strtoll(portstr, &endptr, 10);
                if (*endptr != '\0') {
                    fprintf(stderr, "ERROR: Invalid characters in debug port flag: %s\n", portstr);
                    return EXIT_FAILURE;
                }
                if (port <= 1024 || 65535 < port) {
                    fprintf(stderr, "ERROR: debug server port out of range. We only accept ports above 1024 and below 65535. (got: %"PRIi64")\n", port);
                    return EXIT_FAILURE;
                }
                debugserverport = (MVMuint32)port;
                break;
            }

            case OPT_RAKUDO_HOME:
                option_rakudo_home = argv[argi] + strlen("--rakudo-home=");
                break;

            default:
            argv[new_argc++] = argv[argi];
        }
    }

    /* Move over the remaining arguments. */

    for (; argv[argi]; ++argi) {
        argv[new_argc++] = argv[argi];
    }

#ifdef HAVE_TELEMEH
    unsigned int interval_id = 0;
    char telemeh_inited = 0;
    if (getenv("MVM_TELEMETRY_LOG")) {
        char path[256];
        FILE *fp;
        snprintf(path, 255, "%s.%d", getenv("MVM_TELEMETRY_LOG"),
#ifdef _WIN32
             _getpid()
#else
             getpid()
#endif
             );
        fp = fopen(path, "w");
        if (fp) {
            MVM_telemetry_init(fp);
            telemeh_inited = 1;
            interval_id = MVM_telemetry_interval_start(0, "moarvm startup");
        }
    }
#endif

    /* The +1 is the trailing \0 terminating the string. */
    exec_dir_path_temp = (char*)malloc(exec_path_size + 1);
    memcpy(exec_dir_path_temp, exec_path, exec_path_size + 1);
#ifdef _WIN32
    PathRemoveFileSpecA(exec_dir_path_temp);
    exec_dir_path_size = strlen(exec_dir_path_temp);
    exec_dir_path      = (char*)malloc(exec_dir_path_size + 1);
    memcpy(exec_dir_path, exec_dir_path_temp, exec_dir_path_size + 1);
#else
    exec_dir_path      = dirname(exec_dir_path_temp);
    exec_dir_path_size = strlen(exec_dir_path);
#endif

    /* Retrieve RAKUDO_HOME and NQP_HOME. */

#ifdef STATIC_NQP_HOME
    static_nqp_home = STRINGIFY(STATIC_NQP_HOME);
#endif
    if (!retrieve_home(&nqp_home, nqp_rel_path, nqp_rel_path_size, "NQP_HOME",
            exec_dir_path, exec_dir_path_size, nqp_check_path,
            nqp_check_path_size, static_nqp_home, 0)) {
        fprintf(stderr, "ERROR: NQP_HOME is invalid: %s\n", nqp_home);
        return EXIT_FAILURE;
    }
    nqp_home_size = strlen(nqp_home);

#ifdef STATIC_RAKUDO_HOME
    static_rakudo_home = STRINGIFY(STATIC_RAKUDO_HOME);
#endif
    /* XXX Isn't it time to move RAKUDO_HOME in front of PERL6_HOME?? */
    if (getenv("PERL6_HOME")) {
        if (!retrieve_home(&rakudo_home, perl6_rel_path, perl6_rel_path_size,
                "PERL6_HOME", exec_dir_path, exec_dir_path_size,
                perl6_check_path, perl6_check_path_size, static_rakudo_home,
                option_rakudo_home)) {
            fprintf(stderr, "ERROR: PERL6_HOME is invalid: %s\n", rakudo_home);
            return EXIT_FAILURE;
        }
    }
    else {
        if (!retrieve_home(&rakudo_home, perl6_rel_path, perl6_rel_path_size,
                "RAKUDO_HOME", exec_dir_path, exec_dir_path_size,
                perl6_check_path, perl6_check_path_size, static_rakudo_home,
                option_rakudo_home)) {
            fprintf(stderr, "ERROR: RAKUDO_HOME is invalid: %s\n", rakudo_home);
            return EXIT_FAILURE;
        }
    }
    rakudo_home_size = strlen(rakudo_home);

    /* Put together the lib paths and perl6_file path. */

    lib_path[0] = (char*)malloc(nqp_home_size   + 50);
    lib_path[1] = (char*)malloc(rakudo_home_size + 50);
    lib_path[2] = (char*)malloc(rakudo_home_size + 50);
    perl6_file  = (char*)malloc(rakudo_home_size + 50);

    memcpy(lib_path[0], nqp_home,     nqp_home_size);
    memcpy(lib_path[1], rakudo_home, rakudo_home_size);
    memcpy(lib_path[2], rakudo_home, rakudo_home_size);
    memcpy(perl6_file,  rakudo_home, rakudo_home_size);

#ifdef _WIN32
    strcpy(lib_path[0] +   nqp_home_size, "\\lib");
    strcpy(lib_path[1] + rakudo_home_size, "\\lib");
    strcpy(lib_path[2] + rakudo_home_size, "\\runtime");
#ifdef MOAR_RAKUDO_RUNNER_DEBUG
    strcpy(perl6_file  + rakudo_home_size, "\\runtime\\perl6-debug.moarvm");
#else
    strcpy(perl6_file  + rakudo_home_size, "\\runtime\\perl6.moarvm");
#endif
#else
    strcpy(lib_path[0] +   nqp_home_size, "/lib");
    strcpy(lib_path[1] + rakudo_home_size, "/lib");
    strcpy(lib_path[2] + rakudo_home_size, "/runtime");
#ifdef MOAR_RAKUDO_RUNNER_DEBUG
    strcpy(perl6_file  + rakudo_home_size, "/runtime/perl6-debug.moarvm");
#else
    strcpy(perl6_file  + rakudo_home_size, "/runtime/perl6.moarvm");
#endif
#endif

    /* Start up the VM. */

    instance = MVM_vm_create_instance();

    MVM_vm_set_clargs(instance, new_argc, argv);
    MVM_vm_set_prog_name(instance, perl6_file);
    MVM_vm_set_exec_name(instance, exec_path);
    MVM_vm_set_lib_path(instance, 3, (const char **)lib_path);

    /* Ignore SIGPIPE by default, since we error-check reads/writes. This does
     * not prevent users from setting up their own signal handler for SIGPIPE,
     * which will take precedence over this ignore. */
#ifndef _WIN32
    signal(SIGPIPE, SIG_IGN);
#endif

    if (debugserverport > 0) {
        MVM_debugserver_init(instance->main_thread, debugserverport);

        if (start_suspended) {
            instance->main_thread->gc_status = MVMGCStatus_INTERRUPT | MVMSuspendState_SUSPEND_REQUEST;
        }
    }

    instance->full_cleanup = full_cleanup;

    MVM_vm_run_file(instance, perl6_file);

#ifdef HAVE_TELEMEH
    if (getenv("MVM_TELEMETRY_LOG") && telemeh_inited) {
        MVM_telemetry_interval_stop(0, interval_id, "moarvm teardown");
        MVM_telemetry_finish();
    }
#endif

    free(lib_path[0]);
    free(lib_path[1]);
    free(lib_path[2]);
    free(perl6_file);
#ifndef STATIC_EXEC_PATH
    free(exec_path);
#endif
#if defined(_WIN32) && !defined(STATIC_NQP_HOME)
    /* dirname's return value is either on the stack or is the same pointer
     * that was passed to it depending on the version of libc used, which leads
     * to double frees. */
    free(exec_dir_path);
#endif
    free(exec_dir_path_temp);
#ifndef STATIC_RAKUDO_HOME
    free(rakudo_home);
#else
    if (getenv("PERL6_HOME") || getenv("RAKUDO_HOME"))
        free(rakudo_home);
#endif
#ifndef STATIC_NQP_HOME
    free(nqp_home);
#endif

    if (full_cleanup) {
        MVM_vm_destroy_instance(instance);
        return EXIT_SUCCESS;
    }
    else {
        MVM_vm_exit(instance);
    }
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#include <moar.h>

#ifdef _WIN32
#  include <sys/types.h>
#  include <sys/stat.h>
#  include <process.h>
#  include <shlwapi.h>
#  include <io.h>
#  if defined(_MSC_VER)
#    define strtoll _strtoi64
#  endif
#else
#  include <sys/stat.h>
#  include <libgen.h>
#  include <unistd.h>
#  include "signal.h"
#endif

#define STRINGIFY1(x) #x
#define STRINGIFY(x) STRINGIFY1(x)

/* flags need to be sorted alphabetically */

enum {
    NOT_A_FLAG = -2,
    UNKNOWN_FLAG = -1,

    FLAG_SUSPEND,
    FLAG_FULL_CLEANUP,
    FLAG_TRACING,

    OPT_DEBUGPORT,

    OPT_RAKUDO_HOME,

    OPT_PTY_SPAWN_HELPER
};

static const char *const FLAGS[] = {
    "--debug-suspend",
    "--full-cleanup",
    "--tracing",
};

static int cmp_flag(const void *key, const void *value) {
    return strcmp(key, *(char **)value);
}

static int starts_with(const char *str, const char *want) {
    size_t str_len  = strlen(str);
    size_t want_len = strlen(want);
    return str_len < want_len
        ? 0
        : strncmp(str, want, want_len) == 0;
}

static int parse_flag(const char *arg)
{
    const char *const *found;

    if (!arg || arg[0] != '-')
        return NOT_A_FLAG;

    found = bsearch(arg, FLAGS, sizeof FLAGS / sizeof *FLAGS, sizeof *FLAGS, cmp_flag);

    if (found)
        return (int)(found - FLAGS);
    else if (starts_with(arg, "--debug-port="))
        return OPT_DEBUGPORT;
    else if (starts_with(arg, "--rakudo-home="))
        return OPT_RAKUDO_HOME;
    else if (starts_with(arg, "--pty-spawn-helper="))
        return OPT_PTY_SPAWN_HELPER;
    else
        return UNKNOWN_FLAG;
}

int file_exists(const char *path) {
#ifdef _WIN32
    int             res;
    struct _stat    sb;
    const int       len   = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
    wchar_t * const wpath = (wchar_t *)malloc(len * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, path, -1, (LPWSTR)wpath, len);
    res = _wstat(wpath, &sb);
    free(wpath);
    return res == 0;
#else
    struct stat *sb = malloc(sizeof(struct stat));
    int res         = stat(path, sb) == 0;
    free(sb);
    return res;
#endif
}

void platformify_path(char *path) {
#ifdef _WIN32
    int i;
    for (i = 0; path[i]; i++) {
        if (path[i] == '/') {
            path[i] = '\\';
        }
    }
#endif
}

int retrieve_home(
          char  **out_home,
    const char   *rel_home,
    const size_t  rel_home_size,
    const char   *env_var,
          char   *exec_dir_path,
          size_t  exec_dir_path_size,
    const char   *check_file,
    const size_t  check_file_size,
          char   *static_home,
          char   *options_home
) {
    char   *check_file_path;
    size_t  home_size;
    int     ret;
    char   *env_home         = getenv(env_var);

    if (options_home) {
        *out_home = options_home;
        home_size = strlen(*out_home);
    }
    else if (env_home) {
        home_size = strlen(env_home);
        *out_home = (char*)malloc(home_size + 1);
        strcpy(*out_home, env_home);
#ifdef _WIN32
        if (*(*out_home + home_size - 1) == '\\') {
#else
        if (*(*out_home + home_size - 1) == '/') {
#endif
            *(*out_home + home_size - 1) = '\0';
            home_size--;
        }
    }
    else if (static_home) {
        *out_home = static_home;
        home_size = strlen(*out_home);
    }
    else {
        home_size = exec_dir_path_size + rel_home_size;
        *out_home = (char*)malloc(home_size + 1);
        strncpy(*out_home, exec_dir_path, home_size);
        strncat(*out_home, rel_home, rel_home_size);
        platformify_path(*out_home + exec_dir_path_size);
    }

    check_file_path = (char*)malloc(home_size + check_file_size + 1);
    strncpy(check_file_path, *out_home, home_size + check_file_size);
    strncat(check_file_path, check_file, check_file_size);

    ret = file_exists(check_file_path);
    free(check_file_path);
    return ret;
}

#if defined(_WIN32) && defined(SUBSYSTEM_WINDOWS)
int set_std_handle_to_nul(FILE *file, int fd, BOOL read, int std_handle_type) {
    /* Found on https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2019:

       "When stdin, stdout, and stderr aren't associated with a stream (for example, in a Windows
       application without a console window), the file descriptor values for these streams are
       returned from _fileno as the special value -2. Similarly, if you use a 0, 1, or 2 as the
       file descriptor parameter instead of the result of a call to _fileno, _get_osfhandle also
       returns the special value -2 when the file descriptor is not associated with a stream, and
       does not set errno. However, this is not a valid file handle value, and subsequent calls
       that attempt to use it are likely to fail."

       See https://jdebp.eu/FGA/redirecting-standard-io.html
           https://stackoverflow.com/a/50358201 (Especially the comments of Eryk Sun)
    */
    FILE *stream;
    HANDLE new_handle;

    if (_fileno(file) != -2 || _get_osfhandle(fd) != -2)
        // The handles are initialized. Don't touch!
        return 1;

    /* FD 1 is in an error state (_get_osfhandle(1) == -2). Close it. The FD number is up for grabs
       after this call. */
    if (_close(fd) != 0)
        return 0;

    /* FILE *stdout is in an error state (_fileno(stdout) == -2). Reopen it to a "NUL:" file. This
       will take the next free FD number. So it's important to call this sequentially for FD 0, 1
       and 2. */
    if (freopen_s(&stream, "NUL:", read ? "r" : "w", file) != 0)
        return 0;

    /* Set the underlying Windows handle as the STD handler. */
    new_handle = (HANDLE)_get_osfhandle(fd);
    if (!SetStdHandle(std_handle_type, new_handle))
        return 0;

    return 1;
}
#endif

#if defined(_WIN32) && defined(SUBSYSTEM_WINDOWS)
int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, INT nCmdShow) {
    int argc;
    LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
    char **argv = MVM_UnicodeToUTF8_argv(argc, wargv);
    LocalFree(wargv);
#elif defined(_WIN32) && !defined(SUBSYSTEM_WINDOWS)
int wmain(int argc, wchar_t *wargv[]) {
    char **argv = MVM_UnicodeToUTF8_argv(argc, wargv);
#else
int main(int argc, char *argv[]) {
#endif
    MVMInstance *instance;

    char   *exec_path;
    size_t  exec_path_size;

    char   *exec_dir_path_temp;
    char   *exec_dir_path;
    size_t  exec_dir_path_size;

          char   *nqp_home;
          size_t  nqp_home_size;
          char   *static_nqp_home     = 0;
    const char    nqp_rel_path[14]    = "/../share/nqp";
    const size_t  nqp_rel_path_size   = 13;
    const char    nqp_check_path[28]  = "/lib/NQPCORE.setting.moarvm";
    const size_t  nqp_check_path_size = 27;

          char   *rakudo_home;
          size_t  rakudo_home_size;
          char   *static_rakudo_home    = 0;
          char   *option_rakudo_home    = 0;
    const char    perl6_rel_path[16]    = "/../share/perl6";
    const size_t  perl6_rel_path_size   = 15;
    const char    perl6_check_path[22]  = "/runtime/perl6.moarvm";
    const size_t  perl6_check_path_size = 21;

    char *lib_path[3];
    char *perl6_file;

    int full_cleanup = 0;
    int argi         = 1;
    int flag;
    int new_argc     = 0;

    MVMuint32 debugserverport = 0;
    int start_suspended = 0;

#if defined(__APPLE__) || defined(__Darwin__)
    if (getenv("RAKUDO_DYLD_LIBRARY_PATH")) {
        setenv("DYLD_LIBRARY_PATH", getenv("RAKUDO_DYLD_LIBRARY_PATH"), 0);
    }
#endif

#if defined(_WIN32) && defined(SUBSYSTEM_WINDOWS)
    /* When using the 'windows' subsystem the standard IO handles are not
       connected. This causes a program abort when accessing the handles. To
       prevent these aborts, we redirect the handles to NUL in this case.
    */

    /* Set our own handles. */
    if (!set_std_handle_to_nul(stdin,  0, 1, STD_INPUT_HANDLE))  return EXIT_FAILURE;
    if (!set_std_handle_to_nul(stdout, 1, 0, STD_OUTPUT_HANDLE)) return EXIT_FAILURE;
    if (!set_std_handle_to_nul(stderr, 2, 0, STD_ERROR_HANDLE))  return EXIT_FAILURE;

    /* MoarVM - as a DLL, and the way it's compiled (/MT) has it's own CRT and thus it's own CRT STD handles.
       So MoarVM also needs to fix up its CRT STD handles.
       See: https://docs.microsoft.com/de-de/cpp/c-runtime-library/potential-errors-passing-crt-objects-across-dll-boundaries
            https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features
    */
    if (!MVM_set_std_handles_to_nul()) return EXIT_FAILURE;
#endif

    /* Retrieve the executable directory path. */

#ifdef STATIC_EXEC_PATH
    exec_path = STRINGIFY(STATIC_EXEC_PATH);
    exec_path_size = strlen(exec_path);
#else
    int res;
    exec_path_size = 4096;
    exec_path = (char*)malloc(exec_path_size);
    res = MVM_exepath(exec_path, &exec_path_size);
    while (res < 0 && exec_path_size < 4096*8) {
        exec_path_size *= 2;
        exec_path = (char*)realloc(exec_path, exec_path_size);
        res = MVM_exepath(exec_path, &exec_path_size);
    }
    if (res < 0) {
        fprintf(stderr, "ERROR: Could not retrieve executable path.\n");
        return EXIT_FAILURE;
    }
#endif

    /* Filter out VM arguments from the command line. */

    for (; (flag = parse_flag(argv[argi])) != NOT_A_FLAG; ++argi) {
        switch (flag) {
            case FLAG_FULL_CLEANUP:
            full_cleanup = 1;
            continue;

#if MVM_TRACING
            case FLAG_TRACING:
            MVM_interp_enable_tracing();
            continue;
#endif

            case FLAG_SUSPEND:
            start_suspended = 1;
            continue;

            case OPT_DEBUGPORT: {
                MVMint64 port;
                char *portstr = argv[argi] + strlen("--debug-port=");
                char *endptr;
                port = strtoll(portstr, &endptr, 10);
                if (*endptr != '\0') {
                    fprintf(stderr, "ERROR: Invalid characters in debug port flag: %s\n", portstr);
                    return EXIT_FAILURE;
                }
                if (port <= 1024 || 65535 < port) {
                    fprintf(stderr, "ERROR: debug server port out of range. We only accept ports above 1024 and below 65535. (got: %"PRIi64")\n", port);
                    return EXIT_FAILURE;
                }
                debugserverport = (MVMuint32)port;
                break;
            }

            case OPT_RAKUDO_HOME:
                option_rakudo_home = argv[argi] + strlen("--rakudo-home=");
                break;

            case OPT_PTY_SPAWN_HELPER: {
                char *prog = argv[argi] + strlen("--pty-spawn-helper=");
                char **args = calloc(argc - argi + 1, sizeof(char *));
                args[0] = prog;
                args[argc - argi] = 0;
                for (int argj = 1; argi + argj < argc; argj++)
                    args[argj] = argv[argi + argj];
                // Will not return if all goes well.
                MVM_proc_pty_spawn(prog, args);
                return EXIT_FAILURE;
            }

            default:
            argv[new_argc++] = argv[argi];
        }
    }

    /* Move over the remaining arguments. */

    for (; argv[argi]; ++argi) {
        argv[new_argc++] = argv[argi];
    }

#ifdef HAVE_TELEMEH
    unsigned int interval_id = 0;
    char telemeh_inited = 0;
    if (getenv("MVM_TELEMETRY_LOG")) {
        char path[256];
        FILE *fp;
        snprintf(path, 255, "%s.%d", getenv("MVM_TELEMETRY_LOG"),
#ifdef _WIN32
             _getpid()
#else
             getpid()
#endif
             );
        fp = fopen(path, "w");
        if (fp) {
            MVM_telemetry_init(fp);
            telemeh_inited = 1;
            interval_id = MVM_telemetry_interval_start(0, "moarvm startup");
        }
    }
#endif

    /* The +1 is the trailing \0 terminating the string. */
    exec_dir_path_temp = (char*)malloc(exec_path_size + 1);
    memcpy(exec_dir_path_temp, exec_path, exec_path_size + 1);
#ifdef _WIN32
    PathRemoveFileSpecA(exec_dir_path_temp);
    exec_dir_path_size = strlen(exec_dir_path_temp);
    exec_dir_path      = (char*)malloc(exec_dir_path_size + 1);
    memcpy(exec_dir_path, exec_dir_path_temp, exec_dir_path_size + 1);
#else
    exec_dir_path      = dirname(exec_dir_path_temp);
    exec_dir_path_size = strlen(exec_dir_path);
#endif

    /* Retrieve RAKUDO_HOME and NQP_HOME. */

#ifdef STATIC_NQP_HOME
    static_nqp_home = STRINGIFY(STATIC_NQP_HOME);
#endif
    if (!retrieve_home(&nqp_home, nqp_rel_path, nqp_rel_path_size, "NQP_HOME",
            exec_dir_path, exec_dir_path_size, nqp_check_path,
            nqp_check_path_size, static_nqp_home, 0)) {
        fprintf(stderr, "ERROR: NQP_HOME is invalid: %s\n", nqp_home);
        return EXIT_FAILURE;
    }
    nqp_home_size = strlen(nqp_home);

#ifdef STATIC_RAKUDO_HOME
    static_rakudo_home = STRINGIFY(STATIC_RAKUDO_HOME);
#endif
    /* XXX Isn't it time to move RAKUDO_HOME in front of PERL6_HOME?? */
    if (getenv("PERL6_HOME")) {
        if (!retrieve_home(&rakudo_home, perl6_rel_path, perl6_rel_path_size,
                "PERL6_HOME", exec_dir_path, exec_dir_path_size,
                perl6_check_path, perl6_check_path_size, static_rakudo_home,
                option_rakudo_home)) {
            fprintf(stderr, "ERROR: PERL6_HOME is invalid: %s\n", rakudo_home);
            return EXIT_FAILURE;
        }
    }
    else {
        if (!retrieve_home(&rakudo_home, perl6_rel_path, perl6_rel_path_size,
                "RAKUDO_HOME", exec_dir_path, exec_dir_path_size,
                perl6_check_path, perl6_check_path_size, static_rakudo_home,
                option_rakudo_home)) {
            fprintf(stderr, "ERROR: RAKUDO_HOME is invalid: %s\n", rakudo_home);
            return EXIT_FAILURE;
        }
    }
    rakudo_home_size = strlen(rakudo_home);

    /* Put together the lib paths and perl6_file path. */

    lib_path[0] = (char*)malloc(nqp_home_size   + 50);
    lib_path[1] = (char*)malloc(rakudo_home_size + 50);
    lib_path[2] = (char*)malloc(rakudo_home_size + 50);
    perl6_file  = (char*)malloc(rakudo_home_size + 50);

    memcpy(lib_path[0], nqp_home,     nqp_home_size);
    memcpy(lib_path[1], rakudo_home, rakudo_home_size);
    memcpy(lib_path[2], rakudo_home, rakudo_home_size);
    memcpy(perl6_file,  rakudo_home, rakudo_home_size);

#ifdef _WIN32
    strcpy(lib_path[0] +   nqp_home_size, "\\lib");
    strcpy(lib_path[1] + rakudo_home_size, "\\lib");
    strcpy(lib_path[2] + rakudo_home_size, "\\runtime");
#ifdef MOAR_RAKUDO_RUNNER_DEBUG
    strcpy(perl6_file  + rakudo_home_size, "\\runtime\\perl6-debug.moarvm");
#else
    strcpy(perl6_file  + rakudo_home_size, "\\runtime\\perl6.moarvm");
#endif
#else
    strcpy(lib_path[0] +   nqp_home_size, "/lib");
    strcpy(lib_path[1] + rakudo_home_size, "/lib");
    strcpy(lib_path[2] + rakudo_home_size, "/runtime");
#ifdef MOAR_RAKUDO_RUNNER_DEBUG
    strcpy(perl6_file  + rakudo_home_size, "/runtime/perl6-debug.moarvm");
#else
    strcpy(perl6_file  + rakudo_home_size, "/runtime/perl6.moarvm");
#endif
#endif

    /* Start up the VM. */

    instance = MVM_vm_create_instance();

    MVM_vm_set_clargs(instance, new_argc, argv);
    MVM_vm_set_prog_name(instance, perl6_file);
    MVM_vm_set_exec_name(instance, exec_path);
    MVM_vm_set_lib_path(instance, 3, (const char **)lib_path);

    /* Ignore SIGPIPE by default, since we error-check reads/writes. This does
     * not prevent users from setting up their own signal handler for SIGPIPE,
     * which will take precedence over this ignore. */
#ifndef _WIN32
    signal(SIGPIPE, SIG_IGN);
#endif

    if (debugserverport > 0) {
        MVM_debugserver_init(instance->main_thread, debugserverport);

        if (start_suspended) {
            instance->main_thread->gc_status = MVMGCStatus_INTERRUPT | MVMSuspendState_SUSPEND_REQUEST;
        }
    }

    instance->full_cleanup = full_cleanup;

    MVM_vm_run_file(instance, perl6_file);

#ifdef HAVE_TELEMEH
    if (getenv("MVM_TELEMETRY_LOG") && telemeh_inited) {
        MVM_telemetry_interval_stop(0, interval_id, "moarvm teardown");
        MVM_telemetry_finish();
    }
#endif

    free(lib_path[0]);
    free(lib_path[1]);
    free(lib_path[2]);
    free(perl6_file);
#ifndef STATIC_EXEC_PATH
    free(exec_path);
#endif
#if defined(_WIN32) && !defined(STATIC_NQP_HOME)
    /* dirname's return value is either on the stack or is the same pointer
     * that was passed to it depending on the version of libc used, which leads
     * to double frees. */
    free(exec_dir_path);
#endif
    free(exec_dir_path_temp);
#ifndef STATIC_RAKUDO_HOME
    free(rakudo_home);
#else
    if (getenv("PERL6_HOME") || getenv("RAKUDO_HOME"))
        free(rakudo_home);
#endif
#ifndef STATIC_NQP_HOME
    free(nqp_home);
#endif

    if (full_cleanup) {
        MVM_vm_destroy_instance(instance);
        return EXIT_SUCCESS;
    }
    else {
        MVM_vm_exit(instance);
    }
}


from django.contrib import admin
from sophie.models import Entry, Category, Blog

def delete_selected_action(modeladmin, request, queryset):
    """
    A general action to replace the default 'delete selected objects'
    action in contrib.admin, which does not call obj.delete thus not
    behave as expected
    """
    for obj in queryset:
        obj.delete()
delete_selected_action.short_description = "Delete Selected Items"

def make_entry_live_action(modeladmin, request, queryset):
    queryset.update( status = Entry.LIVE_STATUS )
make_entry_live_action.short_description = "Make Selected Entries Live"

def make_entry_hidden_action(modeladmin, request, queryset):
    queryset.update( status = Entry.HIDDEN_STATUS )
make_entry_hidden_action.short_description = "Make Selected Entries Hidden"

def make_entry_draft_action(modeladmin, request, queryset):
    queryset.update( status = Entry.DRAFT_STATUS )
make_entry_draft_action.short_description = "Make Selected Entries Drafts"

class EntryAdmin(admin.ModelAdmin):

    save_on_top = True

    actions = (
            delete_selected_action,
            make_entry_live_action,
            make_entry_hidden_action,
            make_entry_draft_action,
            )

    prepopulated_fields = { 'slug': ('title',) }

    radio_fields = { 'status': admin.HORIZONTAL }
    fieldsets = (
        ('Entry Content', {
            'fields': (
                ('category', 'author', 'markup'),
                ('title', 'slug'),
                'body',
                'teaser',
                'pub_date',
            )
        }),
        ('Entry Settings', {
            'fields': (
                'status',
                'allow_comment',
            ),
        }),
    )

    list_display = (
        'title',
        'display_category',
        'display_author',
        'status',
        'pub_date'
    )

    list_editable = ('status',)
    list_filter = ('status', 'blog')

    def display_category(self, obj):
        return obj.category.title
    display_category.short_description = "Category"

    def display_author(self, obj):
        return obj.author.get_full_name()
    display_author.short_description = "Author"

    def get_actions(self, request):
        actions = super(EntryAdmin, self).get_actions(request)
        del actions['delete_selected']
        return actions

class CategoryAdmin(admin.ModelAdmin):
    save_on_top = True
    readonly_fields = ('entry_count',)
    prepopulated_fields = {'slug': ('title',)}
    fieldsets = (
        (None, {
            'fields': (
                'blog',
                'title',
                'slug',
                'description',
                'shown',
                'entry_count',
            ),
        }),
    )
    list_display = (
        'title',
        'description',
        'entry_count',
        'display_blog',
        'shown'
    )
    list_filter = ('blog', 'shown')
    list_editable = ('shown', )
    def display_blog(self, obj):
        return obj.blog.title
    display_blog.short_description = "Blog"

class BlogAdmin(admin.ModelAdmin):
    fieldsets = (
        ('Basic Infomation', {
            'fields': (
                ('title', 'slug'),
                'description',
            ),
        }),
        ('Entry Settings', {
            'fields': (
                'full_entry_in_page',
                'highlight_code',
                'page_length',
            ),
            'classes': ('collapse',)
        }),
        ('Feed Settings', {
            'fields': (
                'full_entry_in_feed',
                'feed_length',
                'feed_service'
            ),
            'classes': ('collapse',)
        }),
        ('Services', {
            'fields': (
                'g_analytics_tracking_id',
                'disqus_shortname',
            ),
            'classes': ('collapse',)
        }),
    )
    prepopulated_fields = {'slug': ('title',)}
    list_display = ('title', 'description')

admin.site.register(Entry, EntryAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Blog, BlogAdmin)

from django.contrib import admin
from django import forms
from sophie.models import Entry, Category, Blog

def delete_selected_action(modeladmin, request, queryset):
    """
    A general action to replace the default 'delete selected objects'
    action in contrib.admin, which does not call obj.delete thus not
    behave as expected
    """
    for obj in queryset:
        obj.delete()
delete_selected_action.short_description = "Delete Selected Items"

def make_entry_live_action(modeladmin, request, queryset):
    queryset.update( status = Entry.LIVE_STATUS )
make_entry_live_action.short_description = "Make Selected Entries Live"

def make_entry_draft_action(modeladmin, request, queryset):
    queryset.update( status = Entry.DRAFT_STATUS )
make_entry_draft_action.short_description = "Make Selected Entries Drafts"

class EntryAdminForm(forms.ModelForm):
    class Meta:
        model = Entry
    def __init__(self, *args, **kwargs):
        """Adjust (increase) the size of the TextAreas to make it easier to type a blog entry."""
        super(EntryAdminForm, self).__init__(*args, **kwargs)
        self.fields['body'].widget = forms.widgets.Textarea(attrs={'rows':20, 'cols':110})
        self.fields['teaser'].widget = forms.widgets.Textarea(attrs={'rows':5, 'cols':110})

class EntryAdmin(admin.ModelAdmin):

    save_on_top = True

    actions = (
            delete_selected_action,
            make_entry_live_action,
            make_entry_draft_action,
            )

    prepopulated_fields = { 'slug': ('title',) }

    radio_fields = { 'status': admin.HORIZONTAL }
    fieldsets = (
        ('Entry Content', {
            'fields': (
                ('category', 'author', 'markup'),
                ('title', 'slug'),
                'body',
                'teaser',
                'pub_date',
            )
        }),
        ('Entry Settings', {
            'fields': (
                'status',
                'allow_comment',
            ),
        }),
    )

    list_display = (
        'title',
        'display_category',
        'display_author',
        'status',
        'pub_date'
    )

    list_editable = ('status',)
    list_filter = ('status', 'blog')
    form = EntryAdminForm

    def display_category(self, obj):
        return obj.category.title
    display_category.short_description = "Category"

    def display_author(self, obj):
        return obj.author.get_full_name()
    display_author.short_description = "Author"

    def get_actions(self, request):
        actions = super(EntryAdmin, self).get_actions(request)
        del actions['delete_selected']
        return actions

class CategoryAdmin(admin.ModelAdmin):
    save_on_top = True
    readonly_fields = ('entry_count',)
    prepopulated_fields = {'slug': ('title',)}
    fieldsets = (
        (None, {
            'fields': (
                'blog',
                'title',
                'slug',
                'description',
                'shown',
                'entry_count',
            ),
        }),
    )
    list_display = (
        'title',
        'description',
        'entry_count',
        'display_blog',
        'shown'
    )
    list_filter = ('blog', 'shown')
    list_editable = ('shown', )
    def display_blog(self, obj):
        return obj.blog.title
    display_blog.short_description = "Blog"

class BlogAdmin(admin.ModelAdmin):
    fieldsets = (
        ('Basic Infomation', {
            'fields': (
                ('title', 'slug'),
                'description',
            ),
        }),
        ('Entry Settings', {
            'fields': (
                'full_entry_in_page',
                'highlight_code',
                'page_length',
            ),
            'classes': ('collapse',)
        }),
        ('Feed Settings', {
            'fields': (
                'full_entry_in_feed',
                'feed_length',
                'feed_service'
            ),
            'classes': ('collapse',)
        }),
        ('Services', {
            'fields': (
                'g_analytics_tracking_id',
                'disqus_shortname',
            ),
            'classes': ('collapse',)
        }),
    )
    prepopulated_fields = {'slug': ('title',)}
    list_display = ('title', 'description')

admin.site.register(Entry, EntryAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Blog, BlogAdmin)