gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap/inhibit.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"os"
    26  	"time"
    27  
    28  	"gitee.com/mysnapcore/mysnapd/cmd/snaplock/runinhibit"
    29  	"gitee.com/mysnapcore/mysnapd/i18n"
    30  	"gitee.com/mysnapcore/mysnapd/progress"
    31  	"gitee.com/mysnapcore/mysnapd/usersession/client"
    32  )
    33  
    34  func waitWhileInhibited(snapName string) error {
    35  	hint, err := runinhibit.IsLocked(snapName)
    36  	if err != nil {
    37  		return err
    38  	}
    39  	if hint == runinhibit.HintNotInhibited {
    40  		return nil
    41  	}
    42  
    43  	// wait for HintInhibitedForRefresh set by gate-auto-refresh hook handler
    44  	// when it has finished; the hook starts with HintInhibitedGateRefresh lock
    45  	// and then either unlocks it or changes to HintInhibitedForRefresh (see
    46  	// gateAutoRefreshHookHandler in hooks.go).
    47  	// waitInhibitUnlock will return also on HintNotInhibited.
    48  	notInhibited, err := waitInhibitUnlock(snapName, runinhibit.HintInhibitedForRefresh)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	if notInhibited {
    53  		return nil
    54  	}
    55  
    56  	if isGraphicalSession() {
    57  		return graphicalSessionFlow(snapName, hint)
    58  	}
    59  	// terminal and headless
    60  	return textFlow(snapName, hint)
    61  }
    62  
    63  func inhibitMessage(snapName string, hint runinhibit.Hint) string {
    64  	switch hint {
    65  	case runinhibit.HintInhibitedForRefresh:
    66  		return fmt.Sprintf(i18n.G("snap package %q is being refreshed, please wait"), snapName)
    67  	default:
    68  		return fmt.Sprintf(i18n.G("snap package cannot be used now: %s"), string(hint))
    69  	}
    70  }
    71  
    72  var isGraphicalSession = func() bool {
    73  	return os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != ""
    74  }
    75  
    76  var pendingRefreshNotification = func(refreshInfo *client.PendingSnapRefreshInfo) error {
    77  	userclient := client.NewForUids(os.Getuid())
    78  	if err := userclient.PendingRefreshNotification(context.TODO(), refreshInfo); err != nil {
    79  		return err
    80  	}
    81  	return nil
    82  }
    83  
    84  var finishRefreshNotification = func(refreshInfo *client.FinishedSnapRefreshInfo) error {
    85  	userclient := client.NewForUids(os.Getuid())
    86  	if err := userclient.FinishRefreshNotification(context.TODO(), refreshInfo); err != nil {
    87  		return err
    88  	}
    89  	return nil
    90  }
    91  
    92  func graphicalSessionFlow(snapName string, hint runinhibit.Hint) error {
    93  	refreshInfo := client.PendingSnapRefreshInfo{
    94  		InstanceName: snapName,
    95  		// Remaining time = 0 results in "Snap .. is refreshing now" message from
    96  		// usersession agent.
    97  		TimeRemaining: 0,
    98  	}
    99  
   100  	if err := pendingRefreshNotification(&refreshInfo); err != nil {
   101  		return err
   102  	}
   103  	if _, err := waitInhibitUnlock(snapName, runinhibit.HintNotInhibited); err != nil {
   104  		return err
   105  	}
   106  
   107  	finishRefreshInfo := client.FinishedSnapRefreshInfo{InstanceName: snapName}
   108  	return finishRefreshNotification(&finishRefreshInfo)
   109  }
   110  
   111  func textFlow(snapName string, hint runinhibit.Hint) error {
   112  	fmt.Fprintf(Stdout, "%s\n", inhibitMessage(snapName, hint))
   113  	pb := progress.MakeProgressBar(Stdout)
   114  	pb.Spin(i18n.G("please wait..."))
   115  	_, err := waitInhibitUnlock(snapName, runinhibit.HintNotInhibited)
   116  	pb.Finished()
   117  	return err
   118  }
   119  
   120  var isLocked = runinhibit.IsLocked
   121  
   122  // waitInhibitUnlock waits until the runinhibit lock hint has a specific waitFor value
   123  // or isn't inhibited anymore.
   124  var waitInhibitUnlock = func(snapName string, waitFor runinhibit.Hint) (notInhibited bool, err error) {
   125  	// Every 0.5s check if the inhibition file is still present.
   126  	ticker := time.NewTicker(500 * time.Millisecond)
   127  	defer ticker.Stop()
   128  	for {
   129  		select {
   130  		case <-ticker.C:
   131  			// Half a second has elapsed, let's check again.
   132  			hint, err := isLocked(snapName)
   133  			if err != nil {
   134  				return false, err
   135  			}
   136  			if hint == runinhibit.HintNotInhibited {
   137  				return true, nil
   138  			}
   139  			if hint == waitFor {
   140  				return false, nil
   141  			}
   142  		}
   143  	}
   144  }